c++c++11initializer-listexplicit-constructorin-class-initialization

C++11: in-class initializaton with "= {}" doesn't work with explicit constructor


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.


Solution

  • 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.