Returning references in C++

Since C++ 11 introduces rvalue references, let's see which rules apply when they are returned by functions.

An overview of rvalue references and move semantics is given here. Some of the rules that should be noted are:

Those rules are implicitly used in the following examples.

Let's first define trivial class which is going to be used by the rest of the code.

class X
{
public:

    X() { cout << "X::X()" << endl; }
    X(const X& x) { cout << "X::X(const X&)" << endl; }
    X(X&& x) { cout << "X::X(X&&)" << endl; }
    X& operator=(const X& x) { cout << "X& X::operator=(const X&)" << endl; return *this; }
    X& operator=(X&& x) { cout << "X& X::operator=(const X&)" << endl; return *this; }
    string toString() const { return "string X::toString()"; }
};                    
                

A number of functions will be defined which return values and lvalue/rvalue references. To understand which constructor is called when a reference is returned from a function, three cases are distinguished: value is returned, lvalue reference is returned, rvalue reference is returned.

The first case has three variants:

X val()
{
    return X();
}

X val_lref()
{
    X x;
    return static_cast<X&>(x);
}

X val_rref()
{
    return move(X());
}
                
val() returns temporary value, val_lref() casts a variable to lvalue reference and then returns it, val_rref() casts to rvalue reference (because that's what move really does) and then returns. In each case, X has to be returned, so some of the constructors have to be called (see below for more details).

The second case also has three variants:

X& lref_val()
{
    X x;
    return x;
}

X& lref_lref()
{
    X x;
    return static_cast<X&>(x);
}

const X& lref_rref()
{
    return move(X());
}
                
lref_val() returns lvalue reference to temporary, lref_lref() returns X& & which is equivalent to X&& and lref_rref() returns X& && which is equivalent to X& (const is needed because temporary is bound to the reference).

Third case has two variants:

X&& rref_val()
{
    return X();
}

X&& rref_rref()
{
    return move(X());
}
                
rref_val() returns rvalue reference to temporary value and rref_rref() returns X&& && which is equivalent to X&&. Binding lvalue reference to rvalue reference
X&& rref_lref()
{
    X x;
    return static_cast<X&>(x);
}
                
is not allowed.

Let's see some examples in case the returning value is accepted with variable of type X.

X a1 = val(); // (1)
X a2 = val_lref(); // (2)
X a3 = val_rref(); // (3)
X a6 = lref_rref(); // (4)
X a7 = rref_val(); // (5)
                
For (1) the default constructor is called, X is returned and the return value optimization (RVO) is performed. For (2) the default constructor is called, copy constructor is called, X is returned and RVO is performed. Similar approach is with rvalue reference at (3) - the default constructor is called, move constructor is called, X is returned and RVO is performed. If reference is returned as in (4) then the default constructor is called, X& is returned and the copy constructor is called. Returning rvalue reference produces similar result in (5) - the default is constructor called, X&& is returned and the move constructor is called.

Let's see some examples in case the returning value is accepted with variable of type X&.

const X& b3 = val_rref(); // (6)
const X& b7 = rref_val(); // (7)
                
Such variable has to be constant, because lvalue references cannot bind to temporary variable. For instance, (6) causes the default constructor to be called, then move constructor called, X is returned and a lvalue reference to temporary is created. For the example (7) the default constructor is called, X&& is returned and lvalue reference of the copy of the returned reference is created.

The last group of examples is when the returning value is accepted with variable of type X&&.

X&& c8 = rref_rref(); // (8)
                
So, (8) produces the default constructor to be called, X&& is returned and lvalue reference of the copy of the returned rvalue reference is created.

All samples including those not described in the text can be found in the source code.

references.cpp

Gcc 4.8.1 on Linux is used for testing these samples, but any C++ 11 compliant compiler should work.

ready.

10 print "mail: contact at alepho.com | skype: karastojko | stackoverflow: karastojko | github: karastojko"
20 print "(c) 2009-2023 www.alepho.com"