【问题标题】:Influence of "explicit" constructors in overload resolution“显式”构造函数对重载决议的影响
【发布时间】:2015-02-26 17:10:37
【问题描述】:

为什么下面的代码无法编译,当我在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&lt;short&gt;(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&lt;short&gt;::A(short)B::B(E),他们是模棱两可的?

结束编辑

谢谢 --joja

【问题讨论】:

  • 如果我删除explicit,它也不会编译,它也不应该编译。模棱两可的电话不是A::A,而是F。请注意,A::A(T) 不是 explicit,因此对 F 的调用是模棱两可的。如果您将explicit添加A::A(T),它将排除转换并且代码将编译。
  • 构造函数前面的显式关键字可防止该类型的对象仅使用可充当构造函数参数的参数隐式实例化。当您从 A 的构造函数中删除 'explicit' 时,您可以简单地通过传递 'void F(A)' 一个 short 来构造 A,这就是 F(e1_0) 似乎正在做的事情
  • @5gon12eder,您使用的是哪个编译器?在 VS2013 中,它在没有 explicit 的情况下编译。我知道,模棱两可的调用是对F,但原因是将e1_0 转换为F 的重载接受的类型。当explicit 被删除时,编译器选择B(E) 进行转换并调用F(B)。我添加了explicit 以确保我没有隐式使用构造函数,这是我真实代码中的一个特殊情况的构造函数。在我看来,使用explicit 应该更能引导编译器使用B(E) 转换,而不是考虑将enum E 升级为unsigned long
  • 不想没有&lt;short&gt;(short)A

标签: c++ visual-c++ constructor overload-resolution


【解决方案1】:

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

  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&lt;short&gt; 并调用重载(6),
    • e0 转换为short,通过构造函数(3) 从中创建A&lt;hort&gt; 并调用重载(6) 和
    • 通过构造函数 (5) 从 e0 创建 B 并调用重载 (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&lt;short&gt; 和调用明确引用重载 (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 进行测试,所有五个示例的行为都符合预期。

【讨论】:

  • 感谢@5gon12eder 的回答。我有一些评论: - 第 2 点:构造函数 (2) not 标记为显式,在 VisualC 和 clang 中都没有报告三个可能的重载。 - 第 1 点:使用 (2) 显式调用 f(e0) 我希望调用 (5),因为仅使用用​​户定义的构造函数转换,E -> B(E),因为 (3) 还需要一步: E -> 短 -> A(短)。 - 所以你确认,Visual C++ 在编译第 2 点的示例时有问题。没有引发任何错误消息并静默调用 B(E)?
  • @joja ad point 1: 这对您来说可能看起来很直观,但据我所知,标准中没有规则可以更好地对这些转换序列中的任何一个进行排名比另一个,所以这个电话是模棱两可的。 广告点 2: 我不会急于声称我无法访问的编译器存在错误,但我相当确信我的推理和 GCC 的行为在这里是正确的。也许一位更高级的语言律师会过来并在这个问题上更有权威的评论。
猜你喜欢
  • 1970-01-01
  • 2012-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多