Universal initialization in C++
By Dmitry Kabanov
For my new position, I need to improve my knowledge of the C++ programming language (which I did not use basically since 2010). So I have started reading the book Discovering Modern C++ by Peter Gottschling to get up to speed with, well, modern C++.
In this post, I would like to talk about universal initialization (a.k.a. brace-initialization) that was introduced in C++11.
The syntax for the universal initialization is the following:
int a{}; // Initialize integer variable to zero.
string s{"Hello World!"};
double *pData = new double[3] {0.1, 27.43, 1e-38};
vector<int> values {5, 8, 11};
map<string, string> geo { {"Germany", "Berlin"}, "France", "Paris" };
Note that the whitespace is not important here, but it looks cleaner when there is a space before the opening brace in complicated initializations like in the third line above.
The universal initialization was introduced to overcome the following problems:
- avoid narrowing type errors,
- avoid syntactic ambiguity “is it a function declaration or an object creation?”,
- make initialization of small-size containers slightly easier.
Avoid narrowing type errors. When initializing variable via assingment operator, it is possible to introduce hidden errors like loss of precision, as the compiler narrows the type of the initialization expression to the type of the variable, while this does not happen with the universal initialization:
int x = 7.7; // 7.7 will be truncated to 7 (although a compiler warns about it)
int x {7.7}; // compiler will emit an error; 7.7 must be changed to int(7.7)
Avoid syntactic ambiguity. This is what is called “most vexing parse” in the book “Effective C++” by Scott Meyers. The normal syntax for object initialization is ambiguous and consides with the syntax for function declaration. The universal initialization allows us to avoid this ambiguity, which makes it easier to read for humans.
Test test(); // Is it object initialization or function declaration?
Test test{}; // This is definitely object initialization.
Container initialization.
When you need to initialize a small-size container,
the universal initialization syntax is much shorter.
Say, you need to initialize a vector with four elements.
Then you need to use push-back
method in old-style initialization,
while it is a one-liner with the universal initialization:
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
// In C++11 you can do it more elegant with a one-liner:
vector<int> v{1, 2, 3, 4};