As a general rule, initialization values may be specified with parentheses, an equals sign, or braces.
uniform/braced initialization
struct Widget {
int x{0};
int y = 0;
int z(0); // error!
};
Widget w1; // default ctor
Widget w2 = w1; // copy ctor
w1 = w2; // copy operator=
// uncopyable objects may be intialized using braces or parentheses, but not using =
std::atomic<int> ai1{0};
std::atomic<int> ai2(0);
std::atomic<int> ai3 = 0; // error
It prohibits implicit narrowing conversions among built-in types.
double x, y, z;
int sum1{x + y + z}; // error!
int sum2(x + y + z); // okay (value of expression truncated to an int)
int sum3 = x + y + z; // ditto
Widget w1(10); // call ctor
Widget w2(); // most vexing parse!
Widget w3{}; // call ctor
Braced initialization is the syntax that can be used in the widest variety of contexts, it prevents implicit narrowing conversion, and it’s immune to C++'s most vexing parse.
The pitfall
class Widget {
public:
Widget(int i, bool b);
Widget(int i, double d);
};
Widget w1(10, true); // first ctor
Widget w2{10, true}; // ditto
Widget w3(10, 5.0); // second ctor
Widget w4{10, 5.0}; // ditto
*If there’s any way for compilers to construe a call using a branced initializer to be to a constructor taking a std::initializer_list
, compilers will employ that interpretation.
class Widget {
public:
Widget(int i, bool b);
Widget(int i, double d);
Widget(std::initializer_list<long double> il); // added
};
Widget w1(10, true); // first ctor, as before
Widget w2{10, true}; // std::initializer_list ctor (10 and true convert to long double)
Widget w3(10, 5.0); // second ctor, as before
Widget w2{10, 5.0}; // std::initializer_list ctor (10 and 5.0 convert to long double)
class Widget {
public:
Widget(int i, bool b);
Widget(int i, double d);
Widget(std::initializer_list<long double> il);
operator float() const; // added
};
Widget w5(w4); // copy ctor
Widget w6{w4}; // std::initializer_list ctor (w4 converts to float, and float converts to long double)
class Widget {
public:
Widget(int i, bool b);
Widget(int i, double d);
Widget(std::initializer_list<bool> il);
};
Widget w{10, 5.0}; // error! 10 and 5.0 is convertible but need narrowing conversion
Only if there is no way to convert the types of the arguments in a braced initialzier to the type in std::initializer_list
do compilers fall back on normal overload resolution.
class Widget {
public:
Widget(int i, bool b);
Widget(int i, double d);
Widget(std::initializer_list<std::string> il); // no implicit conversion
};
Widget w1(10, true); // first ctor
Widget w2{10, true}; // first ctor
Widget w3(10, 5.0); // second ctor
Widget w4{10, 5.0}; // second ctor
class Widget {
public:
Widget();
Widget(std::initializer_list<int> il);
};
Widget w1; // default ctor
Widget w2{}; // default ctor
Widget w3(); // most vexing parse
Widget w4({}); // std::initializer_list ctor
Widget w5{{}}; // ditto
Notes
struct Widget {
Widget() = delete;
};
Widget w1; // Error
Widget w2{}; // Ok in C++17, will be fixed in C++20