Consider the following code snippet:
#include <iostream>
struct A {
A() {}
A(const A&) {}
};
struct B {
B(const A&) {}
};
void f(const A&) { std::cout << "A" << std::endl; }
void f(const B&) { std::cout << "B" << std::endl; }
int main() {
A a;
f( {a} ); // A
f( {{a}} ); // ambiguous
f( {{{a}}} ); // B
f({{{{a}}}}); // no matching function
}
Why does each call fabricate the corresponding output? How does the number of braces affect uniform initialization? And how does brace elision affect all this?
Overload resolution is fun like this.
{a}
has exact match rank for initializing (a temporary for) the const A&
parameter, which outcompetes the user-defined conversion B(const A&)
as a realization of {a}
. This rule was added in C++14 to resolve ambiguities in list-initialization (along with adjustments for aggregates).
Note that the notional temporary is never created: after overload resolution picks f(const A&)
, the reference is simply initialized to refer to a
, and this interpretation can apply even for non-copyable types.
const A&
parameter (as above) to the constructor for either A
or B
, so the call is ambiguous.A(const A&)
) repeatedly is prohibited as multiple user-defined conversions—rather than allowing one such conversion per level of overload resolution. So the outermost braces must initialize a B
from the A
initialized from {{a}}
as (permitted) in the second case. (The middle layer of braces could initialize a B
, but copying it with the outer layer would be prohibited and there’s nothing else to try to initialize.)No brace elision is involved—we don’t know the outermost target type to allow it.