C++20 and ReSharper C++
In July of this year, in the German city of Cologne, the ISO WG21 committee approved the full draft of C++20. After Belfast, in November, we now have just one more meeting (in Prague) to deal with national body comments and get their approval. That means that, barring any catastrophes, C++20 will become the current standard sometime after February of next year.
So C++20 is still in the future, but we now have a very good idea of everything that will be in it. Large sections of it have already been implemented in several compilers. GCC and Clang are leading the pack here (EDG is only a front-end) – but MSVC, still shrugging off its historically slowcoach image, is biting at their heels!
Within Visual Studio we can choose between MSVC and clang-cl so, between the two, we have access to a large part of the C++20 surface area today (at time of writing).
But it’s not just the compilers that are getting ahead of the game. ReSharper C++ supports many of these features already, too – often with extra analysis and insights enabled by them.
In this series of articles we’re going to take a tour of some of those features we can use right now, with a focus on those that ReSharper already supports. If you want to follow along, make sure you have enabled
std::c++latest mode. E.g. in Visual Studio 2019 do the following:
Where better to start than with initialization of objects? Being such a bread-and-butter operation you would think that this would be a simple part of the language. In fact it’s where a significant amount of the everyday complexity of C++ hits most people. Over the years, and new standards, more features have been added that introduce new edge cases, interact poorly, or are just incomplete abstractions. To their credit this has been recognised by the committee and a number of new “features” in C++20 are actually about fixing problems with initialization, simplifying it or making it more complete.
The first we’ll look at fixes an embarrassing hole in aggregate initialization. This was an example of how independent features being introduced have interacted poorly. The definition of an aggregate, in C++, has changed across standards. In C++17 a part of the definition was that it has “no user-provided, explicit, or inherited constructors”.
That means that:
is not an aggregate, but:
In the last example the default constructor is deleted. There should be no way to initialize this object … except that these still work:
That’s not what most people want or expect! So p1008 was proposed to fix this, and is now part of C++20.
With p1008, the definition of an aggregate changes again. With regard to constructors it changes from:
no user-provided, explicit or inherited constructors
no user-declared or inherited constructors
This simple change is enough to fix the above issue, as well as a few others (read the paper for more) – while at the same time being a simpler, shorter, rule – usually a good sign, and a direction we’re increasingly trying to go in – even though this is actually a breaking change!
So now, when compiling in C++20 mode, ReSharper C++ correctly flags these initializations as errors:
As an interesting aside, one of the co-authors of p1008 is former JetBrains employee Timur Doumler.
While many initialization related changes going into C++20 are about fixing issues, there are also some new initialization features. The next one, while new, brings C++20 into line with C99. But what is it, and is it exactly the same feature as in C?
One of the problems that C++ has, which it inherited from C, is that parameters to functions (including constructors) are positional. That is they are allocated to arguments purely by their fixed position in the argument list.
This is compact and efficient, but results in code that is less self-documenting – and can lead to subtle bugs if parameters are presented out of order (and this gets worse with overloading). There have been many attempts to address this in libraries over the years. More recently ReSharper C++ (as well as CLion, and most of the JetBrains IDEs) introduced a feature called Parameter Hints.
With Parameter Hints, parameters are annotated with the parameter names as prefix label inline the code (parameter hints are not part of the code, and you don’t type them directly).
Parameter hints work for aggregate initialization, too:
But notice that, in this case, the hints are prefixed with a dot. That’s because this is following Designated Initialization (p0329) syntax – the new C++20 feature we’re talking about. With DI, when initializing an aggregate, you can now write this as:
Note that, unlike with parameter hints,
.m= are now part of the code that you write. Designated Initialization allows you to name the members that you are initializing. As well as improving the self-documenting nature of the code, this also allows you to omit earlier members, while still initializing others:
However, you can’t write them out of order:
That’s different to the C version, and is that way, deliberately, to prevent unexpected evaluation ordering issues – as well as surprises in destructor ordering – both of which are only really an issue in C++.
There are some other differences to C, for mostly the same reasons:
- All or nothing. You can’t mix designator syntax with “classic” positional syntax (although you can skip members, as we saw).
- Designators must be unique – you can’t initialize the same member more than once (and, frankly, why would you want to?)
- Array designated initialization is not supported.
- C++’s brace initialization of members is supported, as well as classic C “equals” initialization.
- Designators cannot be nested (i.e. initializing an aggregate member using nested syntax).
The last one needs a little extra discussion. You can certainly do this:
What you can’t do (but you can in C) is this:
Now, of course, this only works for aggregate initialization, rather than normal function parameters – although you could get some of the way there by taking single structs as arguments instead of individual values. E.g.:
A bit more work, and a bit more awkward, but for some cases this can work nicely.
In general, though, Designated Initialization is another win for readability and expressiveness in C++20. Talking of wins for C++20 – the next version of ReSharper C++, at time of writing, will be 2019.3 – and includes even more C++20 support (including initial Concepts support). And you don’t have to wait – get the EAP now with the button, below.