Today’s reading assignment is another one from Scott Meyers: “Thoughts on the Vagaries of C++ Initialization”. There are a variety of initialization methods, so it is important to understand the distinction between them – especially when using auto.
I initially ran acros sthis article when trying to figure out why auto kept giving me a std::initializer_list in some situations. Hopefully this will prevent you from being as confused as I was.
As Meyers puts it:
Since C++11’s introduction of auto variables and “uniform” braced initialization syntax, it’s been a common error for people to accidentally define a std::initializer_list when they meant to define, e.g., an int.
This example shows you the various initialization syntaxes and their types deduced by auto:
auto x9 = 0; // x9's type is int
auto x10(0); // x10's type is int
auto x11 = {0}; // x11's type is std::initializer_list<int>
auto x12{0}; // x12's type is std::initializer_list<int>
My Highlights
If I want to define a local
intvariable, there are four ways to do it. […] Each syntactic form has an official name:
int x1 = 0; // copy initialization
int x2(0); // direct initialization
int x3 = {0}; // copy list initialization
int x4{0}; // direct list initialization
Don’t be misled by the word “copy” in the official nomenclature. Copy forms might perform moves (for types more complicated than
int), and in practice, implementations often elide both copy and move operations in initializations using the “copy” syntactic forms.
The above ideas form the core of Meyers’s Question #1:
Question #1: Is it good language design to have four ways to say the same thing?
Question #2: Is it good language design to have one of the four syntaxes for defining an int be invalid for defining a
std::atomic<int>?Now let’s suppose we prefer to use
autofor our variable instead of specifying the type explicitly. All four initialization syntaxes compile, but two yieldstd::initializer_list<int>variables instead ofints:
auto x9 = 0; // x9's type is int
auto x10(0); // x10's type is int
auto x11 = {0}; // x11's type is std::initializer_list<int>
auto x12{0}; // x12's type is std::initializer_list<int>
Since C++11’s introduction of auto variables and “uniform” braced initialization syntax, it’s been a common error for people to accidentally define a
std::initializer_listwhen they meant to define, e.g., anint.
The above ideas form the core of Meyers’s Question #3:
Question #3: Is it good language design for copy list initialization (i.e., braces plus “=”) to be treated differently from direct list initialization (i.e., braces without “=”) when deducing the type of
autovariables?
At least people are trying to fix it… somewhat…
The Standardization Committee acknowledged the problem by adopting N3922 into draft C++17. N3922 specifies that an
autovariable, when coupled with direct list initialization syntax and exactly one value inside the braces, no longer yields astd::initializer_list. Instead, it does what essentially every programmer originally expected it to do: define a variable with the type of the value inside the braces. However, N3922 leaves theautotype deduction rules unchanged when copy list initialization is used. Hence, under N3922:
auto x9 = 0; // x9's type is int
auto x10(0); // x10's type is int
auto x11 = {0}; // x11's type is std::initializer_list<int>
auto x12{0}; // x12's type is int
Several compilers have implemented N3922. In fact, it can be hard—maybe even impossible— to get such compilers to adhere to the C++14 standard, even if you want them to. GCC 5.1 follows the N3922 rule even when expressly in C++11 or C++14 modes, i.e., when compiled with
-std=c++11or-std=c++14. Visual C++ 2015 is similar: type deduction is performed in accord with N3922, even when/Za(“disable language extensions”) is used.
Here’s what Meyers says regarding his four theoretical questions:
- Question #1: Having four ways to say one thing constitutes bad design. I understand why C++ is the way it is (primarily backward-compatibility considerations with respect to C or C++98), but four ways to express one idea leads to confusion and, as we’ve seen, inconsistency.
- Question #2: Removing copy initialization from the valid initialization syntaxes makes things worse, because it introduces a seemingly gratuitous inconsistency between
ints andstd::atomic<int>s. - Non-question #3: I thought the C++11 rule about deducing
std::initializer_listsfrom braced initializers was crazy from the day I learned about it. The more times I got bitten by it in practice, the crazier I thought it was. I have a lot of bite marks. - Question #3: N3922 takes the craziness of C++11 and escalates it to insanity by eliminating only one of two syntaxes that nearly always flummox developers. It thus replaces one source of programmer confusion (
auto+ braces yields counterintuitive type deduction) with an even more confusing source (auto+ braces sometimes yields counterintuitive type deduction). One of my earlier blog posts referred to N2640, where deducing astd::initializer_listforautovariables was deemed “desirable,” but no explanation was offered as to why it’s desirable. I think that much would be gained and little would be lost by abandoning the special treatment of braced initializers forautovariables. For example, doing that would reduce the number of sets of type deduction rules in C++ from five to four.
Using C++ Without the Heap
Want to use C++, but worried about how much it relies on dynamic memory allocations? Our course provides a hands-on approach for learning a diverse set of patterns, tools, and techniques for writing C++ code that never uses the heap.
Learn More on the Course Page
