Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++20 designated initializers with templated types

How are designated initializers (C++20) supposed to work with CTAD?

This code works fine in gcc9.2, but fails with clang8

template <typename int_t=int, typename float_t=float>
struct my_pair {
    int_t   first;
    float_t second;
};

template<typename ... ts>
my_pair(ts...) -> my_pair<ts...>;

int main() {
    my_pair x{.first = 20, .second = 20.f};
    static_assert( std::is_same_v<decltype(x.first), int> );
    static_assert( std::is_same_v<decltype(x.second), float> );
}

Is this supposed to be valid?

See an example on https://godbolt.org/z/KtNI43

like image 587
nnolte Avatar asked Sep 11 '19 09:09

nnolte


People also ask

Does C++ have designated Initializers?

Note: out-of-order designated initialization, nested designated initialization, mixing of designated initializers and regular initializers, and designated initialization of arrays are all supported in the C programming language, but are not allowed in C++.

What is designated initializer?

A designated initializer, or designator, points out a particular element to be initialized. A designator list is a comma-separated list of one or more designators. A designator list followed by an equal sign constitutes a designation.

What is designated initializer in Swift?

Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.


1 Answers

Yes, this is supposed to be valid.

The way CTAD works is we perform overload resolution over a synthesized set of constructors to figure out what the class template parameters were. From C++17, that synthesized set of constructors is just based on the primary template's constructors and deduction guides (I'm changing the template parameter names because I find them very confusing):

template <class T=int, class U=float>
struct my_pair {
    T first;
    U second;
};

// default constructor
template <class T=int, class U=float>
auto __f() -> my_pair<T, U>;

// copy candidate
template <class T=int, class U=float>
auto __f(my_pair<T, U>) -> my_pair<T, U>;

// deduction guide
template <class... T>
auto __f(T...) -> my_pair<T...>;

C++20 adds a new aggregate deduction candidate. For each element of either the initializer-list or designated-initializer-list, we pick the corresponding element of the aggregate and use its type as the new candidate. For

my_pair x{.first = 20, .second = 20.f};

The type of first is T and the type of second is U, hence:

// aggregate deduction candidate
template <class T=int, class U=float>
auto __f(T, U) -> my_pair<T, U>;

Now, I wrote these four candidates as functions (because I find it easier to think of them as functions) but the wording defines them as constructors of a hypothetical class type. So when we perform overload resolution using {.first = 20, .second = 20.f}, if you squint it kind of works.

The last candidate is the best candidate (only the aggregate deduction candidate and the deduction guide are viable, the aggregate deduction candidate is more specialized), so we end up with my_pair<int, float>.


Having finished CTAD, we now start over and effectively do

my_pair<int, float> x{.first = 20, .second = 20.f};

Which of course works.

like image 110
Barry Avatar answered Oct 23 '22 04:10

Barry