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
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++.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With