为什么 gcc 和 clang 都会为此程序产生不同的输出? (转换运算符与构造函数)
Why do gcc and clang each produce different output for this program? (conversion operator vs constructor)
程序:
#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
这里哪个编译器是正确的? (或者如果这是某种形式的未定义行为,它们可能都是正确的)
我相信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
)的情况下进行编译,则由于 bar_t b = static_cast<bar_t>(foo);
.
中的复制初始化,clang 会拒绝此代码
程序:
#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
这里哪个编译器是正确的? (或者如果这是某种形式的未定义行为,它们可能都是正确的)
我相信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
)的情况下进行编译,则由于 bar_t b = static_cast<bar_t>(foo);
.