为什么gcc和clang各自为这个程序产生不同的输出?(转换运算符与构造函数)

ver*_*oun 34 c++ gcc clang language-lawyer compiler-bug

程序:

#include <stdio.h>

struct bar_t {
    int value;
    template<typename T>
    bar_t (const T& t) : value { t } {}

    // edit: You can uncomment these if your compiler supports
    //       guaranteed copy elision (c++17). Either way, it 
    //       doesn't affect the output.

    // bar_t () = delete;
    // bar_t (bar_t&&) = delete;
    // bar_t (const bar_t&) = delete;
    // bar_t& operator = (bar_t&&) = delete;
    // bar_t& operator = (const bar_t&) = delete;
};

struct foo_t {
    operator int   () const { return 1; }
    operator bar_t () const { return 2; }
};

int main ()
{
    foo_t foo {};
    bar_t a { foo };
    bar_t b = static_cast<bar_t>(foo);

    printf("%d,%d\n", a.value, b.value);
}
Run Code Online (Sandbox Code Playgroud)

输出为gcc 7/8:

2,2
Run Code Online (Sandbox Code Playgroud)

clang 4/5的输出(也适用于gcc 6.3)

1,1
Run Code Online (Sandbox Code Playgroud)

在创建以下实例时,似乎发生了以下情况bar_t:

对于gcc,calls foo_t::operator bar_t那么constructs bar_t with T = int.

对于铿锵,constructs bar_t with T = foo_t那么calls foo_t::operator int

哪个编译器在这里正确?(或者如果这是某种形式的未定义行为,它们都是正确的)

bam*_*s53 18

我相信clang的结果是正确的.

bar_t a { foo }直接列表初始化和用户定义类型之间的static_cast中,目标类型的构造函数在源类型上的用户定义转换运算符之前被考虑(C++ 14 [dcl.init.list]/3 [expr.static] .cast]/4).如果重载决策找到合适的构造函数,则使用它.

在执行重载时,解决方案bar_t::bar_t<foo_t>(const foo_t&)是可行的,并且对于此模板的任何实例化都将是一个更好的匹配,从而导致在foo上使用强制转换运算符.它也会比任何默认声明的构造函数更好,因为它们采用的不是foo_t,所以bar_t::bar_t<foo_t>使用它.


当前编写的代码依赖于C++ 17保证的副本省略; 如果你编译没有C++ 17的保证副本省略(例如-std=c++14),那么clang会因为拷贝初始化而拒绝这段代码bar_t b = static_cast<bar_t>(foo);.

  • 看起来这是新版gcc(7+)中的一个错误.旧版本(6.3.1)与clang一致.我必须说,我认为我更喜欢有问题的版本,因为它提供了一个额外的配置点(转换运算符)来控制一个类型是如何从另一个构造的. (2认同)