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

Why do gcc and clang each produce different output for this program? (conversion operator vs constructor)

本文关键字:输出 转换 构造函数 运算符 clang gcc 程序 为什么      更新时间:2023-10-16

program:

#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,%dn", a.value, b.value);
}

GCC 7/8 的输出:

2,2

CLANG 4/5 的输出(也适用于 GCC 6.3(

1,1

创建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

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

我相信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 保证复制 elision;如果您在没有 C++17 保证复制省略的情况下进行编译(例如 -std=c++14 (则 clang 由于 bar_t b = static_cast<bar_t>(foo); 中的复制初始化而拒绝此代码。