C++ Logo


Advanced search

Re: [std-proposals] Return Value Optimisation whenever you need it (guaranteed elision)

From: Lénárd Szolnoki <cpp_at_[hidden]>
Date: Mon, 14 Aug 2023 21:30:34 +0100
On Sun, 2023-08-13 at 13:11 -0400, Arthur O'Dwyer wrote:
> On Sat, Aug 12, 2023 at 2:58 PM Lénárd Szolnoki via Std-Proposals
> <std-proposals_at_[hidden]> wrote:
> > On Sat, 2023-08-12 at 08:43 -0700, Thiago Macieira via Std-
> > Proposals wrote:
> > >
> > > int return monkey;
> >
> > In an other forum I floated the idea of using the phrase "for
> > return"
> > here. It got mixed reception.
> >
> > Widget create_widget() {
> > for return Widget w;
> > cook(w);
> > return w;
> > }
> >
> I think the more natural (English-like and less-garden-path-ish)
> place for that syntax would be postfix:
> Widget w for return;
> return w;
> The initializer would go afterward, and (stylistically 100%, maybe
> also physically) forbid anything that doesn't start with `=`, since
> otherwise it would be visually confusing:
> Widget w for return = 42;
> Widget w for return = {1, 2, 3};
> Widget w for return (42); // forbid this, I hope

I wouldn't love if a certain kind of initialization got banned just for
readability reasons. I can see how "<name> for return" is desirable,
maybe allow to parenthesize there.

> However, the obvious problem with this syntax is that it doesn't say
> which `return` statement you mean. It might be possible to elide in
> one `return w;` but not in all of them.

If a variable can be constructed on the return slot then surely the
copy can be elided on all the return statements where the particular
variable is returned.

> Also, copy elision isn't just for `return` anymore. Consider what
> you'd do to indicate mandatory copy elision in
> Widget w = 42;
> co_return w;
> Widget x = 42;
> throw x;
> Would you require them to be annotated with `for co_return` and `for
> throw` respectively?

I don't see why not.

> What would you then do with
> Widget y for return;
> if (cond1) throw y;
> else return y;
> ? Would it be unambiguous that the programmer intended to copy-elide
> the `return y` at the expense of the `throw y`?


> Should the compiler be allowed to copy-elide both (which is, after
> all, physically possible, as long as the evaluation of `cond1`
> doesn't depend on the construction of `y`), or required not to copy-
> elide the `throw`?

It should be allowed if it's somehow possible. Why forbid it?

> I think it's a fool's errand to try to annotate `w`'s declaration to
> express an elision that's going to happen much later on, possibly in
> multiple places. If an annotation is present at all, it should be on
> the `return` or `throw` or `co_return` statements themselves, not on
> some random declaration dozens of lines earlier.

I think there is inevitably spooky action at a distance whether you
annotate the variable declaration or the return statement, but I think
it's worse with annotating the return statement. Consider:

1 Widget foo(int i) {
2 Widget w;
3 if (w.foo(i)) {
4 return [[nrvo]] w;
5 }
6 return Widget(); // prvalue, must be created on return slot
7 }

The above should be ill-formed as the variable that is marked for NRVO
and the object potentially materialised from the prvalue have
overlapping lifetimes, they can't both be created on the return slot at
the same time.

Some of the possible error messages that I envision:
* line 6: can't return prvalue while variable marked for NRVO is in
  * variable x declared on line 2
    * marked for NRVO on line 4

Or a similar error message for line 4, again with a double indirection
to line 6.

Essentially being marked for NRVO becomes a property of the variable,
whether you mark it on the return statement or the variable
declaration. But I think it makes more sense for the variable
properties to be visible on the variable declaration.

> A compiler vendor could simply implement an attribute that, when
> attached to a return/throw/co_return, means "Pretend this copy/move
> isn't here."
> [[nrvo]] return x; // Skip the semantic check that requires `x`
> to be move-constructible. Just compile-error later if the move
> constructor of `x` is actually called.
> [[nrvo]] throw x; // Skip the semantic check that requires `x`
> to be move-constructible. Just compile-error later if the move
> constructor of `x` is actually called.
> This would allow someone to write, non-portably,
> std::mutex GetLockedMutex() {
> std::mutex m;
> m.lock();
> [[nrvo]] return m;
> }

It shouldn't be an attribute IMO, but I guess that's good enough for a

> But it wouldn't make that code portable to all possible platforms.
> (E.g. what about a platform where `std::mutex` is returned in a
> register? Could such a platform hypothetically exist?)

I think this feature should be on par with prvalues in the sense that
is should either the elide copy or trivially copy (or trivially
relocate in the future). It's already possible to observe the trivial
copy of prvalues by using/leaking `this` on the callee side.

Passing in registers (currently) is only really possible with trivially
copyable types. There are some unfortunate ABI mishaps in both the MS
and Itanium ABIs around this, but affects returning prvalues all the

Mandatory elision is also useful in generic code in cases where you
don't want to require movability. It would be awkward to specialise for
triviality in those cases.

> You couldn't ask the compiler to guarantee that copy elision
> happened, because that's dependent on ABI. E.g.:
> template<class T>
> T f() {
> T t;
> escape(&t);
> [[nrvo]] return t;
> }
> On the Itanium ABI, if `T` is a type that must be returned in a
> register, then copy elision can't possibly happen here. But this code
> still seems reasonable to me; in that case `T` is movable, so we
> don't care whether elision happens or not. The only point of the
> attribute would be to say "Hey compiler, please ignore the semantic
> check you'd ordinarily enforce here." As I think Thiago said, this
> attribute would probably be pretty easy to hack into a compiler like
> Clang and try it out.
> my $.02,
> Arthur

Received on 2023-08-14 20:30:42