【问题标题】:Why do gcc and clang give different results in aggregate initialization?为什么 gcc 和 clang 在聚合初始化中给出不同的结果?
【发布时间】:2021-12-29 17:04:31
【问题描述】:
#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

相关问答:Why does Clang 12 refuse to initialize aggregates in the C++20 way?

在这种情况下哪个是正确的?

【问题讨论】:

  • 不知道在 c++20 中哪个是正确的,但是 gcc -std=c++17 打印 1。
  • 虽然 Clang 不支持通过 () 进行聚合初始化可以解释为什么它输出 1,但我不认为它解释了 GCC 是否可以输出 3。也就是说,标准说明 3 过载被认为比 1 过载更好?
  • 对于X&lt;4&gt;X&lt;3&gt; 是比X&lt;1&gt; 更直接的基类。我想是这样。 @NicolBolas

标签: c++ gcc clang c++20 aggregate-initialization


【解决方案1】:

U 是一种声称可以转换为任何其他类型的类型。而“任何其他类型”包括提供给f 模板的任何T

因此,1 始终是潜在的合法重载; SFINAE 允许它存在。

A 是 3 个元素的集合。因此,它可以由包含 0 到 3 个元素的初始化列表进行初始化。 C++20 允许聚合通过构造函数语法进行聚合初始化。

因此,3、2、1 和 0 都是潜在的合法重载; SFINAE 允许它们存在。在 C++20 之前的规则下,这些都不是聚合初始化,因此它们(除了 1,由于 U 的上述属性而起作用)都不是有效的 SFINAE 重载。

Clang 尚未实现 C++20 的聚合初始化规则,因此就 Clang 而言,唯一可用的重载是 X。

对于功能更全的 C++ 编译器,问题是:根据 C++ 的重载解析规则,哪个重载更好?

好吧,所有可用的重载都涉及从参数类型X&lt;4&gt; 到其基类之一的隐式转换。但是,基于基类转换的重载决策优先考虑与继承图中参数类型更接近的基类。因此,X&lt;3&gt; 的优先级高于 X&lt;2&gt;,即使两者都可用。

因此,根据 C++20 的规则和重载决议,3 应该是正确的答案。

【讨论】:

    猜你喜欢
    • 2021-09-25
    • 1970-01-01
    • 2016-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-01
    • 2023-03-10
    相关资源
    最近更新 更多