程序:
#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);
}
输出 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
。
对于 clang ,它constructs bar_t with T = foo_t
然后calls foo_t::operator int
哪个编译器在这里是正确的? (或者如果这是某种形式的未定义行为,它们都是正确的)
答案 0 :(得分:18)
我相信铿锵的结果是正确的。
在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
),那么由于bar_t b = static_cast<bar_t>(foo);
中的复制初始化,clang会拒绝此代码。