【问题标题】:Overload resolution gets different result between gcc and clang重载解析在 gcc 和 clang 之间得到不同的结果
【发布时间】:2016-01-08 03:14:31
【问题描述】:
struct A { A(int);};
struct B { explicit B(A); B(const B&);};
B b({0}); 

gcc 5.1.0 报错

/dev/fd/63:3:8: error: call of overloaded 'B(<brace-enclosed initializer list>)'
 is ambiguous
/dev/fd/63:3:8: note: candidates are:
/dev/fd/63:2:27: note: B::B(const B&)
/dev/fd/63:2:21: note: B::B(A)

而 clang 3.6.0 成功。

哪一个是对的?为什么?

对于 gcc 5.1.0:http://melpon.org/wandbox/permlink/pVe9eyXgu26NEX6X

对于 clang 3.6.0:http://melpon.org/wandbox/permlink/WOi1md2dc519SPW0

这可能类似于Direct list initialization compiles successfully, but normal direct initialization fails, why?,其中 gcc 和 clang 得到相同的结果。

但这是一个不同的问题。 B(A) 在这里是明确的。 gcc 和 clang 得到不同的结果。

【问题讨论】:

标签: c++ c++11 overloading


【解决方案1】:

正确的列表初始化语义是

B b{0};

编译得很好。如果你写B b({0});,gcc无法决定是直接调用B(A)还是创建B{0})然后在第二阶段用B(const B&amp;)复制。这两个选项之间没有优先顺序。

这是语言问题,不是编译器的问题。看到这个gcc bug report

【讨论】:

    【解决方案2】:

    差异可以缩小到

    struct A { explicit A(int); };
    struct B { B(int); };
    void f(A);
    void f(B);
    
    int main() {
        f({ 1 });
    }
    

    根据标准,在 GCC 上这会失败(它表示对于列表初始化,会考虑显式构造函数 - 因此它们可能会产生歧义 - 但不允许选择它们)。 Clang 接受它并调用第二个函数。

    在你的情况下,@Columbo 在他对Direct list initialization compiles successfully, but normal direct initialization fails, why? 的回答中所说的适用。不同的是,在您的情况下,B(const B&amp;); 不再被 Clang 接受,因为 {0} -&gt; B 转换将面临两种可能性:显式构造函数或第二次递归使用复制构造函数。如上所述,第一个选项不会被clang考虑,这次@Columbo的解释适用,复制构造函数不能第二次使用,因为这需要用户定义的转换,因为我们只有一个元素(这里, 0)。所以综上所述,只有第一个构造函数成功并被取走。


    由于我了解这个问题与奇怪的重载解决规则有关,有些可能无法遵循,所以这里有一个更直观的解释。处于活动状态的规则按顺序排列

    • b({0}) 表示转到 http://eel.is/c++draft/dcl.init#17 并从那里转到 http://eel.is/c++draft/over.match.ctor 这是我们的第一个 OR 上下文。枚举的两个构造函数是B(A);B(const B&amp;),参数为{0}

      • 对于B(A),它适用于单个用户定义的转换。

      • 对于B(const B&amp;),我们需要初始化一个const B&amp;,它将我们带到http://eel.is/c++draft/over.ics.list#8,然后到http://eel.is/c++draft/over.ics.ref#2(通过http://eel.is/c++draft/dcl.init#dcl.init.list-3的帮助“否则,如果T是一个引用类型,一个prvalue临时的T 引用的类型是复制列表初始化 ..."),然后是 http://eel.is/c++draft/over.best.ics#over.ics.list-6。生成的 OR 上下文包含候选 B(A);B(const B&amp;),参数为 0。这是我们的第二个 OR 上下文,是 13.3.1.7 的复制列表初始化(根据 over.ics.ref#2 和 dcl.init.list-3 的要求)。

        • 对于B(A),构造函数是显式的,因此被 Clang 忽略(与规范相矛盾)但被 GCC 接受(因此存在歧义)。

        • 对于B(const B&amp;),这是由@Columbo 处理的场景,因此禁止需要的用户定义转换。较新的草稿不再有此规则(但可能会重新添加)。但是因为 0const B&amp; 将是一个普通的用户定义转换(不是列表初始化),它会忽略转换所需的显式构造函数(对于复制构造函数的这种潜在的第二次使用),因此无论如何,用户定义的转换是不可能的,并且该规则的重要性远不如我在编写上述简短摘要时所想的那么重要。

    因此,对于 GCC,它可以直接使用显式构造函数,此外还可以单独使用复制构造函数。对于clang,它只考虑直接使用显式构造函数,它不会像GCC那样使用复制构造函数通过复制列表初始化间接使用它。两者都不会考虑第二次使用复制构造函数,在这里无关紧要。

    【讨论】:

      猜你喜欢
      • 2012-08-14
      • 2021-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-20
      相关资源
      最近更新 更多