"explicit"构造函数对过载解决的影响

Influence of "explicit" constructors in overload resolution

本文关键字:解决 影响 explicit 构造函数      更新时间:2023-10-16

为什么下面的代码不编译,当我删除类 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_testexp_construct_test.cpp(23): error C2668: 'F' : ambiguous call to overloaded function
1>          e:exp_construct_testexp_construct_test.cpp(19): could be 'void F(A<short>)'
1>          e:exp_construct_testexp_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++中有一个错误,当我从A(unsigned long)中删除explicit时,编译器无论意图选择B(E(还是引发歧义错误。任何人都可以确认 clang 行为符合标准,而 VC++ 有缺陷吗?

我添加了

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

并像这样调用 G:

G(e1_0);

这不会引发任何错误。为什么这里G(E)是预先的,而对于A<short>::A(short)B::B(E)的候选人,他们是模棱两可的?

结束编辑

谢谢--乔亚

让我们一个接一个地看一下您的示例的各种变体。

  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>并调用重载 (6( 和
    • 通过构造函数从e0创建B (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)
    };
    

    这三个选项保持不变,但这一次,这三个选项都适用,调用(甚至更加(模棱两可,程序格式不正确。

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

    这一次,这三条转换路径都不适用,因为每条路径都将通过一个explicit构造函数。 没有适用的f过载,程序格式不正确。

  5. 呼叫g(e0) .

    我们在这里有两种可能性:

    • 调用过载 (8( 没有任何转换或
    • e0转换为short并调用重载 (9(。

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

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