C++ Logo

std-proposals

Advanced search

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

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Sun, 13 Aug 2023 13:11:18 -0400
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

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.
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? 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`?

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.

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;
    }

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?)

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-13 17:11:33