Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Influence of "explicit" constructors in overload resolution

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

like image 874
joja Avatar asked Oct 20 '22 16:10

joja


1 Answers

Let's look at the various variations of your example one after the other.

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

    • convert e0 to an unsigned long, create an A<short> from it via constructor (2) and call overload (6),
    • convert e0 to short, create an A<hort> from it via constructor (3) and call overload (6) and
    • create a B 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.

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

  3. 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).

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

  5. Call g(e0).

    We have two possibilities here:

    • Call overload (8) without any conversion or
    • convert 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.

like image 59
5gon12eder Avatar answered Oct 22 '22 23:10

5gon12eder