When is RVO garanteed to apply / does apply with C++20 compilers

465 Views Asked by At

The C++ core guidelines states that

F.20: For “out” output values, prefer return values to output parameters

But then gives the following exception:

struct Package {      // exceptional case: expensive-to-move object
    char header[16];
    char load[2024 - 16];
};

Package fill();       // Bad: large return value
void fill(Package&);  // OK

Isn't it supposed to be a case where the return value optimization kicks in ? Is RVO prevented in this case ? Or still not as efficient as passing by reference ? Or is it that some compilers don't manage to do it ?

More generally, when should I rely on the compiler optimizing return values as efficiently as the pass-by-reference technique ?

2

There are 2 best solutions below

1
T.C. On BEST ANSWER

"Plain" RVO (i.e., returning a prvalue or "temporary" in common parlance) is guaranteed in C++17 and well-supported even before that.

NRVO (i.e., returning a local variable) can be finicky and is not guaranteed, and if it's not performed then you get a move instead. If your move is expensive, you may want to avoid that.

In the example, there's a decent chance that fill needs to use the latter.

0
eerorika On

Or still not as efficient as passing by reference ?

If RVO applies, then it is equally efficient to return a value, as it is to use an output reference.

Is RVO prevented in this case?

No. Being "big" does not prevent the object from being RVO'd.

When is RVO garanteed to apply / does apply with C++20 compilers

A case where it does not apply:

... A return statement can involve an invocation of a constructor to perform a copy or move of the operand if it is not a prvalue or if its type differs from the return type of the function.

So, it depends on the implementation of the function whether copy-elision is guaranteed.

The guidelines indeed fail to explain why the recommendation should be followed.

Note that the exception says:

Exceptions

If a type is expensive to move (e.g., array<BigPOD>), consider allocating it on the free store and return a handle (e.g., unique_ptr), or passing it in a reference to non-const target object to fill (to be used as an out-parameter).

The highlighted suggestion in the exception makes more sense to me. It makes it clear that the object is too big for stack, and thus reduces the chance stack overflows.