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?".
“l-value” refers to a memory location that identifies an object. “r-value” refers to the data value that is stored at some address in memory. References in C++ are nothing but the alternative to the already existing variable.
Such a reference is called an lvalue reference to a const value (sometimes called a reference to const or a const reference). In the above program, we bind const reference ref to modifiable lvalue x . We can then use ref to access x , but because ref is const, we can not modify the value of x through ref .
In the example, the main function passes an rvalue to f . The body of f treats its named parameter as an lvalue. The call from f to g binds the parameter to an lvalue reference (the first overloaded version of g ). You can cast an lvalue to an rvalue reference.
In this example, the rvalue reference a can be bound to the temporary initialized with the rvalue expression 2 , but the rvalue reference b cannot be bound to the lvalue expression i . You can bind the rvalue reference c to the temporary value 1.0 that is converted from the variable i . End of C++11 only.
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. However A(B const &, C const &)
remains in the overload set if either (or both) arguments are lvalues.
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