"显式"构造函数对重载决策的影响

joj*_*oja 5 c++ constructor visual-c++ overload-resolution

为什么以下代码不能编译,当我在A类中的构造函数之前删除显式关键字时,它编译?

使用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);
}
Run Code Online (Sandbox Code Playgroud)

错误:

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 ==========
Run Code Online (Sandbox Code Playgroud)

编辑:我下载了clang并使用clang-cl编译,报告了两种情况的错误.正如评论中所指出的那样,暧昧关系在A<short>(short)和之间B(E).

所以VC++中可能存在一个错误,当我explicit从中删除时A(unsigned long),编译器会以任何意图选择B(E)而不是引发一个模糊错误.任何人都可以确认clang行为符合标准,VC++是否有错误?

我补充道

void G(E) {};
void G(short) {};
Run Code Online (Sandbox Code Playgroud)

并像这样打电话给G:

G(e1_0);
Run Code Online (Sandbox Code Playgroud)

这不会引起任何错误.为什么这里G(E)被prefferred,并在候选人的情况下,A<short>::A(short)B::B(E),他们是暧昧?

结束编辑

谢谢--joja

5go*_*der 3

让我们逐个看看示例的各种变体。

  1. 原始示例调用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)
    
    Run Code Online (Sandbox Code Playgroud)

    在这三种可能性中

    • 转换e0为 an unsigned longA<short>通过构造函数 (2) 创建 an 并调用重载 (6),
    • 转换e0为,通过构造函数 (3) 从它short创建一个并调用重载 (6) 和A<hort>
    • 创建Bfrom e0via 构造函数 (5) 并调用重载 (7)

    第一个选项不适用,因为 (2) 是explicit。其余两个都涉及用户定义的转换,这两个转换被认为同样好,并且没有一个会偏向另一个。调用不明确且程序格式错误。

  2. explicit让我们从构造函数中删除并调用f(e0)

    template<typename T>
    struct A
    {
      A();  // (1)
      A(unsigned long);  // (2)
      A(T);  // (3)
    };
    
    struct B
    {
      B();  // (4)
      B(E);  // (5)
    };
    
    Run Code Online (Sandbox Code Playgroud)

    三个选项保持不变,但这一次,所有三个选项都适用,并且调用(甚至更)不明确,并且程序格式不正确。

  3. 让我们创建构造函数explicit并调用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)
    };
    
    Run Code Online (Sandbox Code Playgroud)

    这使得隐式构造 an 变得不可能A<short>,并且调用明确地引用重载 (5)。

  4. 让我们也B创建 的构造函数explicit并调用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)
    };
    
    Run Code Online (Sandbox Code Playgroud)

    这次,三个转换路径都不explicit适用,因为每个转换路径都会经过构造函数。没有f适用的过载,并且该程序格式不正确。

  5. 称呼g(e0)

    我们这里有两种可能性:

    • 无需任何转换即可调用重载 (8) 或
    • 转换e0为 ashort并调用重载 (9)。

    在这两个选项中,第一个选项显然是有利的,因为它不涉及转换。这个呼吁是明确的。(即使构造函数 (5) 不是explicit。)

请注意,默认构造函数 (1) 和 (4) 实际上对本讨论没有任何贡献。使用 GCC 4.9.1 进行测试,所有五个示例的行为均符合预期。