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

时间:2015-02-26 17:10:38

标签: c++ visual-c++ constructor 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);
}

错误:

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 ==========

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

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

我添加了

void G(E) {};
void G(short) {};

并且像这样打电话给G:

G(e1_0);

哪个不会引发任何错误。 为什么这里G(E)会受到制服,如果是候选人A<short>::A(short)B::B(E),他们是不明确的?

结束修改

由于 --joja

1 个答案:

答案 0 :(得分: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)
    

    在三种可能性中

    • e0转换为unsigned long,通过构造函数(2)从中创建A<short>并调用重载(6),
    • e0转换为short,通过构造函数(3)从中创建A<hort>并调用overload(6)和
    • 通过构造函数(5)从B创建e0并调用overload(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)
    };
    

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

  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)
    };
    

    这使得无法隐式构造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)
    };
    

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

  5. 致电g(e0)

    我们有两种可能性:

    • 调用过载(8)而不进行任何转换或
    • e0转换为short并调用重载(9)。

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

  6. 请注意,默认构造函数(1)和(4)实际上对此讨论没有任何贡献。使用GCC 4.9.1进行测试,所有五个示例都按预期运行。