Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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

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?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Which is correct in this case?

>Solution :

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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading