6

For example, code like this:

struct A { A(int); };
struct B { B(A);   };

int main()
{
    B b{{0}}; // OK
    B c({0}); // error
}

The error messages are:

f.cc: In function 'int main()':
f.cc:7:9: error: call of overloaded 'B(<brace-enclosed initializer list>)' is ambiguous
  B c({0}); // error

         ^
f.cc:7:9: note: candidates are:
f.cc:2:12: note: B::B(A)
 struct B { B(A);   };
        ^
f.cc:2:8: note: constexpr B::B(const B&)
 struct B { B(A);   };
        ^
f.cc:2:8: note: constexpr B::B(B&&)
M.M
  • 138,810
  • 21
  • 208
  • 365
Jack Chin
  • 525
  • 4
  • 12
  • Yes, it should be public. I have changed that, thanks! – Jack Chin Sep 09 '15 at 02:26
  • 1
    Surprisingly, this fails, even when I delete copy and move constructors of B: http://coliru.stacked-crooked.com/a/7fb30cd9d5839ac2 Compilation error message is rather baffling, it is ambiguous with deleted functions. – Chris Beck Sep 09 '15 at 02:35
  • 2
    @ChrisBeck deleted functions participate in overload resolution, so it is not baffling that deleting them makes no difference – M.M Sep 09 '15 at 02:37
  • B b({{{0}}}); This compiles, I have no idea why. Voodoo perhaps? – Fibbs Sep 09 '15 at 02:42
  • I think the brace form of constructing `{}` never assumes an *initializer list* but the paren version `()` always does, even when there is no *initializer list* constructor. This answer gives some info, but not a definitive answer. https://stackoverflow.com/questions/29851502/properly-initialising-variables-in-modern-c-c11-and-above-using-or/29851993#29851993 – Galik Sep 09 '15 at 03:17

1 Answers1

8

As of the latest official standard, C++14, your first initialization is not ambiguous. [over.match.list]:

enter image description here

As no initializer-list constructors exist, we enter the "second phase". And now consider [over.best.ics]/4:

enter image description here

Our element is {0}. Hence this disallows the (user-defined) conversion {0} -> A for the copy constructor. Clearly, this doesn't apply if we aren't in the second phase of [over.match.list], so for your example with B c({0}), no list-initialization occurs for c and both constructors are considered.


CWG issue 1467

The first initialization is currently just as ambiguous as the second one. Compilers simply haven't implemented CWG #1467 yet - its resolution removed bullet point (4.5), quoted above.
See #2076, which opts to revert the change:

The resolution of issue 1467 made some plausible constructs ill-formed. For example,

struct A { A(int); };
struct B { B(A); };
B b{{0}};

This is now ambiguous, because the text disallowing user-defined conversions for B's copy and move constructors was removed from 13.3.3.1 [over.best.ics] paragraph 4.

"The text" is the aforementioned bullet point. Richard Smith proposes the following wording:

For non-class types, we allow initialization from a single-item list to perform a copy only if the element within the list is not itself a list (13.3.3.1.5 [over.ics.list] bullet 9.1). The analogous rule for this case would be to add back the bullet in 13.3.3.1 [over.best.ics] paragraph 4, but only in the case where the initializer is itself an initializer list:

        the second phase of 13.3.1.7 [over.match.list] when the initializer list has exactly one         element that is itself an initializer list, where the target is the first parameter of a constructor
        of class X, and the conversion is to X or reference to (possibly cv-qualified) X,

As the initializer {0} is itself an initializer list, that bullet point would make your first initialization well-formed again.

Columbo
  • 60,038
  • 8
  • 155
  • 203