将此答案视为起点。我们有相同的初始三个候选人:
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...>;
And the aggregate deduction candidate is based on the actual initializer-list or designated-initializer-list we provide, not the actual underlying members of the aggregate. Our designated-initializer-list is {.second = 20.f}
so our aggregate deduction candidate becomes:
// aggregate deduction candidate
template <class T=int, class U=float>
auto __f(U) -> my_pair<T, U>;
The template parameters always come from the primary class template, so we bring in the default template arguments from there. The candidate arguments come from the initializer-list, and the type of second
is U
.
The aggregate deduction 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{.second = 20.f};
Which works, and leads to x.first
being initialized from {}
.
CTAD for aggregates was only adopted very recently (at the Cologne meeting in July 2019, two months ago). Before that feature, this would still have been well-formed:
my_pair{.second = 20.f};
Why? We don't yet have the aggregate deduction candidate, but we still do have the deduction guide... which is viable. It gives us my_pair<float>
. Which is to say, my_pair<float, float>
once you fill in the default template argument for U
.
That's why gcc is giving you the behavior you see - it simply does not yet implement CTAD for aggregates, and is giving you the old behavior.