Search code examples
c++pass-by-referencemove-semanticsrvalue-referencedefault-arguments

Using rvalue references for default arguments


I want to make a function that takes an optional reference to an object, and creates one for the duration of the function if it is not provided, i.e.

void Foo(Bar& b = Bar()) { /* stuff */ }

This is, of course, invalid code, as a Bar cannot be implicitly converted to a Bar reference in this context. The reference can't be const, as b is mutated inside the function.

You can get around this by using an rvalue reference, i.e.

void Foo(Bar&& b = Bar()) { /* stuff */ }

Is this a valid use of rvalue references? Callers now have to call std::move on their Bar arguments, even though I have no intention of clearing the passed Bar, as is usually the case when you are passing rvalues.


Solution

  • void Foo(Bar&& b = Bar()) { /* stuff */ }
    

    That's certainly a valid use of r-value references, but it does not in any way reflect the actual semantics, and is thus "broken by design".

    What you want to do, is use a forwarder-function supplying the default argument like this:

    void Foo(Bar& b) { /* stuff */ }
    void Foo() { Bar b{}; Foo(b); }
    

    Or use a static default-argument (Beware that this always reuses the same object):

    template<class T> decltype(T{})& default_object() {static T x{}; return x;}
    void Foo(Bar& b = default_object<Bar>()) { /* stuff */ }
    

    Or like KerrekSB proposes in a comment (I added constexpr) use this dangerous template function:

    template<class T> constexpr T& no_move(T&& t) { return t; }
    void Foo(Bar& b = no_move(Bar{})) { /* stuff */ }