为什么 gcc 和 clang 在聚合初始化中给出不同的结果?
Why do gcc and clang give different results in aggregate initialization?
#include <iostream>
struct U
{
template<typename T>
operator T();
};
template<unsigned I>
struct X : X<I - 1> {};
template<>
struct X<0> {};
template<typename T>
constexpr auto f(X<4>) -> decltype(T(U{}, U{}, U{}, U{}), 0u) { return 4u; }
template<typename T>
constexpr auto f(X<3>) -> decltype(T(U{}, U{}, U{}), 0u) { return 3u; }
template<typename T>
constexpr auto f(X<2>) -> decltype(T(U{}, U{}), 0u) { return 2u; }
template<typename T>
constexpr auto f(X<1>) -> decltype(T(U{}), 0u) { return 1u; }
template<typename T>
constexpr auto f(X<0>) -> decltype(T{}, 0u) { return 0u; }
struct A
{
void* a;
int b;
double c;
};
int main() { std::cout << f<A>(X<4>{}) << std::endl; }
上面的代码被 gcc 和 clang 接受。但是,gcc 给出了预期的输出 3
;除了 clang.
给出的意外输出 1
参见:https://godbolt.org/z/YKnxWah1a
相关问答:
在这种情况下,哪个是正确的?
U
是一种声称可以转换为任何其他类型的类型。 “任何其他类型”包括提供给 f
模板的任何 T
。
因此,1 始终是潜在的合法重载; SFINAE 允许它存在。
A
是 3 个元素的集合。因此,它可以由包含 0 到 3 个元素的初始化列表进行初始化。 C++20 允许聚合使用构造函数语法进行聚合初始化。
因此,3、2、1 和 0 都是潜在的合法重载; SFINAE 允许它们存在。在 C++20 之前的规则下,其中的 none 将是聚合初始化,因此其中的 none (保存 1,由于前面提到的 属性 of U
) 将是有效的 SFINAE 重载。
Clang 尚未实现 C++20 的聚合初始化规则,因此就 Clang 而言,唯一可用的重载是 X<1>。
对于功能更全的 C++ 编译器,问题是:根据 C++ 的重载解析规则,哪种重载更好?
好吧,所有可用的重载都涉及从参数类型 X<4>
到其基类 class 之一的隐式转换。但是,基于 base class 转换的重载决策优先考虑更接近继承图中参数类型的 base classes。因此,X<3>
优先于 X<2>
,即使两者都可用。
因此,根据 C++20 的规则和重载决议,3 应该是正确答案。
#include <iostream>
struct U
{
template<typename T>
operator T();
};
template<unsigned I>
struct X : X<I - 1> {};
template<>
struct X<0> {};
template<typename T>
constexpr auto f(X<4>) -> decltype(T(U{}, U{}, U{}, U{}), 0u) { return 4u; }
template<typename T>
constexpr auto f(X<3>) -> decltype(T(U{}, U{}, U{}), 0u) { return 3u; }
template<typename T>
constexpr auto f(X<2>) -> decltype(T(U{}, U{}), 0u) { return 2u; }
template<typename T>
constexpr auto f(X<1>) -> decltype(T(U{}), 0u) { return 1u; }
template<typename T>
constexpr auto f(X<0>) -> decltype(T{}, 0u) { return 0u; }
struct A
{
void* a;
int b;
double c;
};
int main() { std::cout << f<A>(X<4>{}) << std::endl; }
上面的代码被 gcc 和 clang 接受。但是,gcc 给出了预期的输出 3
;除了 clang.
1
参见:https://godbolt.org/z/YKnxWah1a
相关问答:
在这种情况下,哪个是正确的?
U
是一种声称可以转换为任何其他类型的类型。 “任何其他类型”包括提供给 f
模板的任何 T
。
因此,1 始终是潜在的合法重载; SFINAE 允许它存在。
A
是 3 个元素的集合。因此,它可以由包含 0 到 3 个元素的初始化列表进行初始化。 C++20 允许聚合使用构造函数语法进行聚合初始化。
因此,3、2、1 和 0 都是潜在的合法重载; SFINAE 允许它们存在。在 C++20 之前的规则下,其中的 none 将是聚合初始化,因此其中的 none (保存 1,由于前面提到的 属性 of U
) 将是有效的 SFINAE 重载。
Clang 尚未实现 C++20 的聚合初始化规则,因此就 Clang 而言,唯一可用的重载是 X<1>。
对于功能更全的 C++ 编译器,问题是:根据 C++ 的重载解析规则,哪种重载更好?
好吧,所有可用的重载都涉及从参数类型 X<4>
到其基类 class 之一的隐式转换。但是,基于 base class 转换的重载决策优先考虑更接近继承图中参数类型的 base classes。因此,X<3>
优先于 X<2>
,即使两者都可用。
因此,根据 C++20 的规则和重载决议,3 应该是正确答案。