为什么 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; }

上面的代码被 gccclang 接受。但是,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 应该是正确答案。