Home > Back-end >  Why do gcc and clang give different results in aggregate initialization?
Why do gcc and clang give different results in aggregate initialization?

Time:12-30

#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; }

The code above is accepted by both of gcc and clang. However, gcc gives the expected output 3; other than the unexpected output 1 given by clang.

See: https://godbolt.org/z/YKnxWah1a

Related Q&A: Why does Clang 12 refuse to initialize aggregates in the C 20 way?

Which is correct in this case?

CodePudding user response:

U is a type which claims convertibility to any other type. And "any other type" includes whatever T is provided to the f templates.

Therefore, 1 is always a potentially legitimate overload; SFINAE permits it to exist.

A is an aggregate of 3 elements. As such, it can be initialized by an initializer list containing anywhere from 0 to 3 elements. C 20 allows aggregates to undergo aggregate initialization with constructor syntax.

Therefore, 3, 2, 1, and 0 are all potentially legitimate overloads; SFINAE permits them to exist. Under pre-C 20 rules, none of these would be aggregate initialization, so none of them (save 1, which works because of the aforementioned property of U) would be valid SFINAE overloads.

Clang doesn't implement C 20's rule for aggregate initialization as of yet, so as far as Clang is concerned, the only available overload is X<1>.

For more fully functional C compilers, the question is this: which overload is better by C 's overload resolution rules?

Well, all of the available overloads involve implicit conversions from the argument type X<4> to one of its base classes. However, overload resolution based on base class conversions gives priority to base classes that are closer to the type of the argument in the inheritance graph. As such, X<3> is given priority over X<2> even though both are available.

Therefore, by C 20's rules and overload resolution, 3 ought to be the right answer.

  • Related