In the below code I call step
as a member function and as a global function on a temporary value. The member function is allowed, and works, whereas the global function is disallowed due to invalid initialisation of non-const reference of type ‘kludge&’ from an rvalue of type ‘kludge’
.
I'm trying to understand, from a language perspective, why one behaviour is allowed and the other is not. Technically both calls and functions seem like they'd be compiled identically, or at least could be.
#include <iostream>
struct kludge {
int a;
kludge() {
a = 1;
}
kludge & step() {
a++;
std::cout << a << ",";
return *this;
}
};
kludge get() {
kludge t;
return t;
}
kludge & step( kludge & t ) {
t.a++;
std::cout << t.a << ",";
return t;
}
int main() {
get().step();
step( get() );
}
You cannot bind rvalues to non-const lvalue references1. That applies to step(get())
as the parameter of step
, which is a non-const lvalue reference, cannot be bound to the prvalue (pure rvalue) get()
.
However, member functions can per se be called on object arguments of every value category, be it lvalue or rvalue - [over.match.funcs]/4 and /5:
For non-static member functions, the type of the implicit object parameter is
- “lvalue reference to cv
X
” for functions declared without a ref-qualifier or with the&
ref-qualifier[..]
For non-static member functions declared without a ref-qualifier, an additional rule applies:
- even if the implicit object parameter is not
const
-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter. [ Note: The fact that such an argument is an rvalue does not affect the ranking of implicit conversion sequences (13.3.3.2). — end note ]
But if you use so-called ref-qualifiers, you can restrict the value categories that are valid for a particular member function. That is, if you write:
kludge & step() & { /* .. */ }
The call get().step()
will be ill-formed too.
A reference to type “cv1
T1
” is initialized by an expression of type “cv2T2
” as follows:
- If the reference is an lvalue reference and the initializer expression
- is an lvalue [..]
- has a class type (i.e.,
T2
is a class type), whereT1
is not reference-related toT2
, and can be implicitly converted to an lvalue of type “cv3T3
,” [..]
- Otherwise, the reference shall be an lvalue reference to a non-volatile
const
type (i.e., cv1 shall beconst
), or the reference shall be an rvalue reference.
Temporary cannot bind to non-const reference
step( get() );
// ~~~~~ Creates a temporary object (r-value)
// But step( ) excepts a non-const reference
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