Many of the following are excerpts from Scott Meyers - Effective Modern C++
Benefits
Initializing variables with {} has the following benefits:
- It can be used to initialize non-static members in a class(while parentheses cannot);
- It would prevent/warn narrowing conversions among built-in types. For example, you cannot initialize an integer by summing doubles(while parenthess and = operator do not check that);
- It is immune to C++’s most vexing parse.
Most Vexing Parse
What is most vexing parse? It is really vexing in the look, and you’ll shout at how stupid the compiler is:
1 |
|
Such a seemingly simple program would not compile. This is because A a()
is treated as a function rather than a struct instance, and that function does not have doSomething
method of course.
Why the compiler seems in this way? This might because C does not support constructor calls, and writing with parantheses will let the language think this is a function call.
To give another example from wikipedia:
1 |
|
The intent is to construct a local variable by converting from a double to an int type. However, this will be treated in compiler as “a function named i
whose return type is int
, which takes a parameter of type int
whose name is adouble
.
We have some ways to deal with this:
int i((int(adouble)))
Wrap parantheses;int i((int) adouble)
orint i(static_cast<int>(adouble))
cast;int i{int{adouble}}
braced initialization. However braced initialization does not allow, or will warn about the narrow conversions in this case.
Unexpected Behaviors
Some points to watch out for when using the braced initialization: In this post, it is mentioned that when you use auto
with the brace initializer, the type would be deduced as std::initializer_list
, which may give you behavior you don’t want.
Also in the function overloading case, if one or more constructors declare a parameter of type std::initializer_list
, if you use brace initialization to initialize an object, you would in have a huge chance using the std::initializer_list
constructor over others, even when other constructors seem far more suitable. For example:
1 |
|
As you can see, the std::initializer_list
constructor overshadows other constructors in many cases. This will requires class writers and clients(callers) to take extra attention in making sure the added constructor guarantees backward compatibility, and the objects are using the correct constructor. This can sometimes be confusing for std::vector
, as we have two constructors for it:
1 |
|