struct A { A(int);};
struct B { explicit B(A); B(const B&);};
B b({0});
gcc 5.1.0 gives the error
/dev/fd/63:3:8: error: call of overloaded 'B(<brace-enclosed initializer list>)'
is ambiguous
/dev/fd/63:3:8: note: candidates are:
/dev/fd/63:2:27: note: B::B(const B&)
/dev/fd/63:2:21: note: B::B(A)
while clang 3.6.0 succeeds.
Which one is right? Why?
For gcc 5.1.0: http://melpon.org/wandbox/permlink/pVe9eyXgu26NEX6X
For clang 3.6.0: http://melpon.org/wandbox/permlink/WOi1md2dc519SPW0
This may be similar to Direct list initialization compiles successfully, but normal direct initialization fails, why? which gcc and clang get same result.
But this is a different question. B(A)
is explicit here. gcc and clang get different results.
The difference can be reduced to
struct A { explicit A(int); };
struct B { B(int); };
void f(A);
void f(B);
int main() {
f({ 1 });
}
On GCC this fails, in accordance to the Standard (which says that for list initialization, explicit constructors are considered - so they can yield an ambiguity - but they just are not allowed to be selected). Clang accepts it and calls the second function.
In your case, what @Columbo says in his answer to Direct list initialization compiles successfully, but normal direct initialization fails, why? applies. With the difference that in your case, B(const B&);
is not anymore acceptable to Clang because the {0} -> B
conversion would be faced with two possibilities: the explicit constructor or using the copy constructor recursively a second time. The first option, as explained above, will not be considered by clang and this time the explanation by @Columbo applies and the copy constructor cannot be used a second time because that would need a user defined conversion as we have a single element (here, 0
). So in the summary, only the first constructor succeeds and is taken.
Since I understand the issue is about weird overload resolution rules and some might not be able to follow, here's a more intuitive explanation. The rules that are active are, in order
b({0})
means goto http://eel.is/c++draft/dcl.init#17 and from there to http://eel.is/c++draft/over.match.ctor which is our first OR context . The two constructors enumerated are B(A);
and B(const B&)
with argument {0}
.
For B(A)
it works with a single user defined conversion.
For B(const B&)
, we need to initialize a const B&
which brings us to http://eel.is/c++draft/over.ics.list#8 then to http://eel.is/c++draft/over.ics.ref#2 (by help of http://eel.is/c++draft/dcl.init#dcl.init.list-3 "Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is copy-list-initialized ...") then to http://eel.is/c++draft/over.best.ics#over.ics.list-6. The resulting OR context has candidates B(A);
and B(const B&)
, with the argument 0
. This is our second OR context and is copy-list-initialization by 13.3.1.7 (as required by over.ics.ref#2 and dcl.init.list-3).
For B(A)
, the constructor is explicit and therefore ignored by Clang (in contradiction with the spec) but accepted by GCC (hence the ambiguity).
For B(const B&)
, this is the scenario handled by @Columbo and therefore the user-defined conversion which would be needed is forbidden. Newer drafts don't have this rule anymore (but probably it will be added back). But because the 0
to const B&
would be a normal user-defined conversion (not a list initialization), it would ignore the explicit constructor needed for the conversion anyway (for this potential second use of the copy constructor), and therefore the user-defined conversion wouldn't be possible anyway and the rule is of much less significance than I thought when I wrote the above shorter summary.
Therefore for GCC it can use the explicit constructor directly, and in addition by a single use of the copy constructor. For clang, it only considers using the explicit constructor directly, and it won't use it indirectly by a copy-list-initialization using the copy constructor like GCC does. Both won't consider using the copy constructor a second time, and it's irrelevant here.
The correct list initialization semantics is
B b{0};
which compiles fine. If you write B b({0});
, the gcc can't decide if call B(A)
directly or create B
({0}
) and then copy it with B(const B&)
in the second phase. There is no priority ordering between these two options.
It's language problem, not compiler's problem. See this gcc bug report.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With