Why there is no ambiguity?
struct B {};
struct C {};
struct A
{
A(B const &, C const &) {}
A(B const &&, C const &&) = delete;
#if 0
A(B const &, C const &&) = delete;
A(B const &&, C const &) = delete;
#endif
};
B const b() { return {}; } // const result type may make sense
C const c() { return {}; } // for some user-defined types
int main()
{
A a0{B{}, C{}}; // I want to prohibit this
A a1{b(), c()}; // and this cases
B const bb{};
C const cc{};
A a2{b(), cc}; // But surely I also want to prohibit this
A a3{bb, c()}; // and this cases to compile
}
Here I want to store lvalue const references to B
and C
instances into the instance of A
. Of course I want to ensure, that lifetime of referenced objects overcomes the lifetime of A
's instance.
To achieve this I just = delete;
overloadings for B const &&
and C const &&
, which in addition matches B &&
and C &&
correspondingly.
Above approach works perfectly for conversion-constructors (i.e. unary ones). But it turns out, that for higher arities I have to explicitly = delete;
all the combinatorically possible combinations, which encloses const rvalue reference versions of parameters of interest (i.e. #if 1
).
Then I think: "Why there is no an ambiguity?", — because the ambiguity should also prevent compilation of wrong code in the above case.
So question is: "Why there is no ambiguity for mixed case of constructor's invocation?".
tl;dr: It was re-designed that way.
Under the original move proposal your code would be ambiguous. Under that proposal, lvalues could bind to rvalue references, but would prefer an lvalue reference if it existed in the overload set.
Fairly late in the process, as more people began to understand the proposal, and as concepts were still being considered for C++11, the rules were changed so that lvalues could not bind to rvalue references.
I personally did not feel this change was necessary, but far more people liked the change than not liked it, and the basic functionality of move semantics would work either way. So this was definitely a compromise worth making as opposed to not getting move semantics at all.
With the change that lvalues can not bind to rvalue references,
A(B const &&, C const &&)
is not part of the overload resolution set if either argument is an lvalue. HoweverA(B const &, C const &)
remains in the overload set if either (or both) arguments are lvalues.