In C++11 we can do in-class initialization using a "brace-or-equal-initializer" (words from the standard) like this:
struct Foo
{
/*explicit*/ Foo(int) {}
};
struct Bar
{
Foo foo = { 42 };
};
But if we un-comment explicit
, it no longer compiles. GCC 4.7 and 4.9 say this:
error: converting to ‘Foo’ from initializer list would use explicit constructor ‘Foo::Foo(int)’
I found this surprising. Is it really the intention of the C++11 standard that this code doesn't compile?
Removing the =
fixes it: Foo foo { 42 };
but I personally find this harder to explain to people who have been used to the form with =
for decades, and since the standard refers to a "brace-or-equal-initializer" it's not obvious why the good old way doesn't work in this scenario.
I can't explain the rationale behind this, but I can repeat the obvious.
I found this surprising. Is it really the intention of the C++11 standard that this code doesn't compile?
§13.3.1.7
In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.
Removing the = fixes it:
Foo foo { 42 };
but I personally find this harder to explain to people who have been used to the form with = for decades, and since the standard refers to a "brace-or-equal-initializer" it's not obvious why the good old way doesn't work in this scenario.
Foo foo { 42 }
is direct initialization, whereas the equal sign (with braces) makes it copy-list-initialization. Another answer reasons that because compilation fails for copy-initialization (equal sign without braces), then it shouldn't be surprising that it also fails for copy-list-initialization, but the two fail for different reasons.
cppreference:
Direct-initialization is more permissive than copy-initialization: copy-initialization only considers non-explicit constructors and user-defined conversion functions, while direct-initialization considers all constructors and implicit conversion sequences.
And their page on the explicit specifier:
Specifies constructors and (since C++11) conversion operators that don't allow implicit conversions or copy-initialization.
On the other hand, for copy-list-initialization:
T object = {arg1, arg2, ...}; (10)
10) on the right-hand-side of the equals sign (similar to copy-initialization)
Otherwise, the constructors of T are considered, in two phases:
- If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that consists of the elements of the braced-init-list, with the restriction that only non-narrowing conversions are allowed. If this stage produces an explicit constructor as the best match for a copy-list-initialization, compilation fails (note, in simple copy-initialization, explicit constructors are not considered at all)
As discussed in What could go wrong if copy-list-initialization allowed explicit constructors?, the compilation fails because the explicit constructor is selected but is not allowed to be used.