why does the following code not compile, and when I remove the explicit Keyword before the constructor in class A, it compiles?
Using Visual Studio 2013:
enum E { e1_0, e1_1 };
template<typename T>
struct A
{
A() {}
explicit A(unsigned long) {}
A(T) {}
};
struct B
{
B() {}
B(E) {}
};
void F(B) {};
void F(A<short>) {};
void test()
{
F(e1_0);
}
Error:
1>------ Build started: Project: exp_construct_test, Configuration: Debug Win32 ------
1> exp_construct_test.cpp
1>e:\exp_construct_test\exp_construct_test.cpp(23): error C2668: 'F' : ambiguous call to overloaded function
1> e:\exp_construct_test\exp_construct_test.cpp(19): could be 'void F(A<short>)'
1> e:\exp_construct_test\exp_construct_test.cpp(18): or 'void F(B)'
1> while trying to match the argument list '(E)'
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
Edit: I downloaded clang and compiled with clang-cl which reports the error for both cases. So as has been pointed out in commentaries, the ambiguouity is between A<short>(short)
and B(E)
.
So perhaps there is a bug in VC++, that when I remove explicit
from A(unsigned long)
, the compiler by whatever intention chooses B(E) instead to raise an ambiguouity error. Can anyone confirm the clang behavior as standard compliant, and VC++ as buggy?
I added
void G(E) {};
void G(short) {};
and a call to G like this:
G(e1_0);
Which doesn't raise any error.
Why here G(E)
is prefferred, and in case of candidates A<short>::A(short)
and B::B(E)
, they are ambiguous?
End Edit
Thanks --joja
Let's look at the various variations of your example one after the other.
The original example calling f(e0)
.
enum E {e0, e1};
template<typename T>
struct A
{
A(); // (1)
explicit A(unsigned long); // (2)
A(T); // (3)
};
struct B
{
B(); // (4)
B(E); // (5)
};
void f(A<short>); // (6)
void f(B); // (7)
void g(E); // (8)
void g(short); // (9)
Among the three possibilities
e0
to an unsigned long
, create an A<short>
from it via constructor (2) and call overload (6),e0
to short
, create an A<hort>
from it via constructor (3) and call overload (6) andB
from e0
via constructor (5) and call overload (7)the first option is not applicable because (2) is explicit
. The remaining two both involve a user-defined conversion which are considered equally good and none is taken in favor of the other. The call is ambiguous and the program ill-formed.
Let's remove the explicit
from the constructor and call f(e0)
.
template<typename T>
struct A
{
A(); // (1)
A(unsigned long); // (2)
A(T); // (3)
};
struct B
{
B(); // (4)
B(E); // (5)
};
The three options remain the same but this time, all three are applicable and the call is (even more) ambiguous and the program ill-formed.
Let's make both constructors explicit
and call f(e0)
.
template<typename T>
struct A
{
A(); // (1)
explicit A(unsigned long); // (2)
explicit A(T); // (3)
};
struct B
{
B(); // (4)
B(E); // (5)
};
This makes it impossible to implicitly construct an A<short>
and the call unambiguously refers to overload (5).
Let's make B
's constructor explicit
too and call f(e0)
.
template<typename T>
struct A
{
A(); // (1)
explicit A(unsigned long); // (2)
explicit A(T); // (3)
};
struct B
{
B(); // (4)
explicit B(E); // (5)
};
This time, none of the three conversion paths is applicable because each one would go through an explicit
constructor. There is no overload of f
that is applicable and the program is ill-formed.
Call g(e0)
.
We have two possibilities here:
e0
to a short
and call overload (9).Among these two, the first option in clearly favorable because it does not involve a conversion. The call is unambiguous. (Even if constructor (5) is not explicit
.)
Note that the default constructors (1) and (4) actually don't contribute anything to this discussion. Testing with GCC 4.9.1, all five examples behave as expected.
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