C++ Logo


Advanced search

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

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Sun, 13 Aug 2023 20:10:18 +0100
On Sun, Aug 13, 2023 at 6:11 PM Arthur O'Dwyer wrote:
> Widget w for return = 42;
> Widget w for return = {1, 2, 3};
> Widget w for return (42); // forbid this, I hope

I think the following would be better:
    for return Widget w = 42;
    for return Widget w = {1, 2, 3};
    for return Widget w(42);

> What would you then do with
> Widget y for return;
> if (cond1) throw y;
> else return y;

I hadn't even given consideration to throwing an
unmovable-and-uncopiable object. Not even sure how that would work...
I remember I tried to learn about how exceptions work under the hood
on Linux x86_64 but I just couldn't follow the whole 'personality
function' thing. I still don't really know how they work. So for now
I'll just talk about returning an unmovable-and-uncopiable object from
a function. So if we have a function with the following signature:

    mutex Func(void);

then I propose that there are two acceptable forms of return statement
from this function. The first one is where 'return' is followed by a
prvalue, as follows:

    mutex Func(void)
        return mutex();

And then the second form is where 'return' isn't followed by anything
at all. In this circumstance, there must have been a prior local
variable defined as "for return", as follows:

    mutex Func(void)
        for return mutex mtx;
        return; // This is not a typo

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

When I was writing my paper on 'nrvo', I had to get a rudimentary
understanding of a dozen or so instruction sets and calling
conventions (which was really fun when I discovered that the stack
grew upwards on SuperH -- then I was playing around with a
cross-compiler and Qemu). Some of the calling conventions will always
try to return a struct in registers if it can fit -- but when dealing
with an unmovable-and-uncopiable class, all calling conventions always
use memory instead of registers. This is because you are allowed to
take the address of a mutex (so it can't be in registers). Hmmmm.....
I wonder if we were to delete the unary ampersand operator (i.e. the
'addressof' operator) for an unmovable-and-uncopiable class, then
perhaps could a mutex reside in registers . ? . ? . ?
Just as an example, I think on the Microsoft x64 calling convention,
if you return a "pair<int,int>" from a function, or a similarly-sized
struct, it will be returned in registers. But if you make the struct
unmovable and uncopiable, it will come back in RAM.

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

So this could become:

template<class T>
T f(void)
    for return T t;
    return; // This is not a typo

Furthermore I think we should be able to have a function such as the following:

recursive_mutex Func(void)
    if ( some_global_boolean )
        for return recursive_mutex m;
        m.lock(); m.lock(); m.lock(); m.lock();
    else if ( SomeFunction() )
        for return recursive_mutex m;

    return recursive_mutex();

Received on 2023-08-13 19:10:32