8

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 ?

Bérenger
  • 2,678
  • 2
  • 21
  • 42

2 Answers2

4

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.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
4

"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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Ok I understand now. Fill will presumably construct a default object (so a named object) and then initialize it (with e.g. `std::fill`), which mean we are in the NRVO case, which is not garanteed to be optimized – Bérenger Oct 21 '19 at 20:37