Search code examples
c++referencervalue-referencervaluelvalue

Classes, Rvalues and Rvalue References


An lvalue is a value bound to a definitive region of memory whereas an rvalue is an expression value whose existence is temporary and who does not necessarily refer to a definitive region of memory. Whenever an lvalue is used in a position in which an rvalue is expected, the compiler performs an lvalue-to-rvalue conversion and then proceeds with evaluation.

http://www.eetimes.com/discussion/programming-pointers/4023341/Lvalues-and-Rvalues

Whenever we construct a temporary (anonymous) class object or return a temporary class object from a function, although the object is temporary, it is addressable. However, the object still is a valid rvalue. This means that the object is a) an addressable rvalue or b) is being implicitly converted from an lvalue to an rvalue when the compiler expects an lvalue to be used.

For instance:

class A
{
public:
    int x;
    A(int a) { x = a; std::cout << "int conversion ctor\n"; }
    A(A&) { std::cout << "lvalue copy ctor\n"; }
    A(A&&) { std::cout << "rvalue copy ctor\n"; }
};
A ret_a(A a) 
{
    return a;
}

int main(void)
{
    &A(5); // A(5) is an addressable object
    A&& rvalue = A(5); // A(5) is also an rvalue
}

We also know that temporary objects returned (in the following case a) by functions are lvalues as this code segment:

int main(void)
{
    ret_a(A(5));
}

yields the following output:

int conversion ctor

lvalue copy ctor

Indicating that the call to the function ret_a using actual argument A(5) calls the conversion constructor A::A(int) which constructs the function's formal argument a with the value 5.

When the function completes execution, it then constructs a temporary A object using a as its argument, which invokes A::A(A&). However, if we were to remove A::A(A&) from the list of overloaded constructors, the returned temporary object would still match the rvalue-reference constructor A::A(A&&).

This is what I'm not quite understanding: how can the object a match both an rvalue reference and an lvalue reference? It is clear that A::A(A&) is a better match than A::A(A&&) (and therefore a must be an lvalue). But, because an rvalue reference cannot be initialized to an lvalue, given that the formal argument a is an lvalue, it should not be able to match the call to A::A(A&&). If the compiler is making an lvalue-to-rvalue conversion it would be trivial. The fact that a conversion from 'A' to 'A&' is also trivial, both functions should have identical implicit conversion sequence ranks and therefore, the compiler should not be able to deduce the best-matching function when both A::A(A&) and A::A(A&&) are in the overloaded function candidate set.

Moreover, the question (which I previously asked) is:

How can a given object match both an rvalue reference and an lvalue reference?


Solution

  • For me:

    int main(void)
    {
        ret_a(A(5));
    }
    

    Yields:

    int conversion ctor
    rvalue copy ctor
    

    (i.e. rvalue, not lvalue). This is a bug in your compiler. However it is understandable as the rules for this behavior changed only a few months ago (Nov. 2010). More on this below.

    When the function completes execution, it then constructs a temporary A object using a as its argument, which invokes A::A(A&).

    Actually no. When the function ret_a completes execution, it then constructs a temporary A object using a as its argument, which invokes A:A(A&&). This is due to [class.copy]/p33]1:

    When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. — end note ]

    However if you remove the A::A(A&&) constructor, then A::A(&) will be chosen for the return. Although in this case, then the construction of the argument a will fail because you can't construct it using an rvalue. However ignoring that for the moment, I believe your ultimate question is:

    How can a given object match both an rvalue reference and an lvalue reference?

    in referring to the statement:

    return a;
    

    And the answer is in the above quoted paragraph from the draft standard: First overload resolution is tried as if a is an rvalue. And if that fails, overload resolution is tried again using a as an lvalue. This two-stage process is tried only in the context wherein copy elision is permissible (such as a return statement).

    The C++0x draft has just recently been changed to allow the two-stage overload resolution process when returning arguments that have been passed by value (as in your example). And that is the reason for the varying behavior from different compilers that we are seeing.