C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Forwarding Reference can also accept PRvalue (not just Lvalue or Rvalue)

From: SD SH <Z5515zwy_at_[hidden]>
Date: Tue, 21 Oct 2025 12:01:47 +0000
Then how will you implement it?

Think of this:
```
SYSCLASS struct Accessor; // unmovable and uncopyable

extern SYSAPI Accessor (*access)();

int main() {
    StoreAndGet<Accessor>(access());
}
```
We don't know what the function will do, and it's impossible to change the function. We only know that access() will construct a Accessor object and follow the calling convention, then we must call copy or move constructor, but in this case we can't do it.

For your example, if a function returns a unmovable and uncopyable object means that you shouldn't construct it by existing objects in your code.

»ñÈ¡Outlook for Android<https://aka.ms/AAb9ysg>
________________________________
From: Std-Proposals <std-proposals-bounces_at_[hidden]> on behalf of Frederick Virchanza Gotham via Std-Proposals <std-proposals_at_[hidden]>
Sent: Monday, October 20, 2025 10:24:34 PM
To: std-proposals <std-proposals_at_[hidden]>
Cc: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Subject: [std-proposals] Forwarding Reference can also accept PRvalue (not just Lvalue or Rvalue)

This thread is a follow-on from the thread "New intrinsic type --
prvalue". I have changed the thread name as I no longer wish to create
a new intrinsic type, nor do I want to have any kind of new keyword
such as 'prvalue'.

Let's start with a use case. We want to implement the 'emplace' method
of 'std::optional' as follows:

    template<typename T>
    class optional {
        alignas(T) char unsigned buf[sizeof(T)];
    public:
        template<typename... Params>
        T &emplace(Params&&... args)
        {
            return *::new(this->buf) T( forward<Params>(args)... );
        }
    };

And now let's say that we have a 3rd party library function something
like the following:

    std::mutex SomeLibraryFunc(int const n, std::string &s)
    {
        s += " ";
        s += std::to_string(n);
        return std::mutex();
    }

And here's what we try to do inside 'main':

    int main(void)
    {
        std::string str("monkey");
        optional<std::mutex> om;
        om.emplace( SomeLibraryFunc(7, str) );
    }

As you can see in the following GodBolt, if we put it all together, it
fails to compile:

    https://godbolt.org/z/85K8azafs

It fails to compile at the following expression inside the 'emplace' method:

     ::new(this->buf) T( forward<Params>(args)... );

This line fails because an "std::mutex" is neither copyable nor movable.

My proposal is as follows: Wherever you have a template function with
a forwarding reference for a parameter, whereas previously the
forwarding reference could accommodate either an Lvalue or an Rvalue,
well with this new language change it can also accommodate a PRvalue.
If you pass a PRvalue as an argument to 'emplace', for example:

        Example 1: om.emplace( std::mutex() );
        Example 2: om.emplace( SomeLibraryFunc(7, str) );

The compiler will pass the details-of-how-to-produce-the-PRvalue to
the template function by providing a new unique instantiation of the
template function. So the previous GodBolt would be implemented by the
compiler as though 'emplace' had been written as follows:

    template<typename Invokable, typename... Params>
    T &emplace(Invokable &&f, Params&&... args)
    {
        return *::new(this->buf) T( forward<Invokable>(f)(
forward<Params>(args)...) );
    }

And as if the invocation of 'emplace' had been done as follows:

    om.emplace( SomeLibraryFunc, 7, str ); // previously:
om.emplace( SomeLibraryFunc(7, str) );

Here's a GodBolt showing specifically what I intend:

    https://godbolt.org/z/GjbaM9jTe

Now of course there are a few things to consider here before
assimilating this change into the language. For example:

    - What happens if the PRvalue is mentioned more than once inside
the body of the template function?
    - What happens if the PRvalue is mentioned in a context other than
it being used as an argument to a constructor?
    - What if the first line of 'SomeLibraryFunc' locks a mutex or
acquires a semaphore? Previously the locking took place before
'emplace' was entered, but now the locking will take place after
'emplace' is entered.

There will be a few issues like this to explore. I think one
requirement for this new feature would be that the body of the
template function must only mention the PRvalue once and that it must
be passing it as an argument to a constructor. Perhaps allow multiple
mentions of the PRvalue if there are separate control paths, for
example:

    template<typename T>
    T &StoreAndGet(int x, T &&arg)
    {
        alignas(T) static char unsigned buf[ sizeof(T) ];

        if ( x > 7 )
        {
            return *::new(buf) T( forward<T>(arg) );
        }
        else
        {
            alignas(T) static char unsigned other_buf[ sizeof(T) ];
            return *::new(other_buf) T( forward<T>(arg) );
        }
    }

The whole point of all of this though is that the header file
containing the implementation of 'emplace' won't have to change.

Consider another example function:

    template<typename T>
    T &StoreAndGet(T &&arg)
    {
        alignas(T) static char unsigned buf[ sizeof(T) ];
        return *::new(buf) T( forward<T>(arg) );
    }

We won't be able to pass a mutex to this function because a mutex is
both uncopyable and unmovable, but with the new language change, if we
pass a PRvalue as the argument, we end up with the compiler
implementing something like the following under the hood:

    https://godbolt.org/z/hn6WaGfbc

The proposal has two aims:
    (1) Allow the passing of unmovable-and-uncopyable types
    (2) Remove unnecessary copy/move operations
--
Std-Proposals mailing list
Std-Proposals_at_[hidden]
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2025-10-21 12:01:54