C++ Logo

std-proposals

Advanced search

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

From: Jens Maurer <jens.maurer_at_[hidden]>
Date: Sun, 16 Jul 2023 17:58:30 +0200
On 16/07/2023 17.39, Frederick Virchanza Gotham via Std-Proposals wrote:
> I reply to Jason and Arthur in series below.
>
>
> On Sun, Jul 16, 2023 at 4:15 PM Jason McKesson wrote:
>>
>> Frederick Virchanza Gotham wrote:
>>> Next I will combine this code with my previous code so that we can:
>>> Step 1 : Return a locked mutex by value from a function
>>> Step 2 : Have that locked mutex placed inside a global std::optional variable
>>
>> That's neat and all, but that doesn't really address the core issues.
>> Nobody claimed that the functionality was unimplementable, so showing
>> a way of doing it through inline assembly isn't really addressing any
>> of the things people are talking about.
>
>
>
> Yeah but if I implement it in inline assembly for System V x86_64,
> Microsoft x64, cdecl, stdcall, aarch64, arm32, then I think it will
> get a lot more attention when people see how easily these
> implementations can simply slide into the C++26 standard library
> without any changes to the core language.

I don't like having this as a magic library function with plenty of
callbacks. If this is worth having in the standard, let's make sure
the core language offers the relevant facility.

(Being able to manually cause the named return value optimization
might be helpful beyond returning locked mutexes.)

Jens


> On Sun, Jul 16, 2023 at 4:03 PM Arthur O'Dwyer wrote:
>>
>> You still can't get such an object into a std::list<mutex>, or even into a variable of
>> type `mutex` on the stack. (You say "it's" totally safe to do, but you seem to have
>> locked onto the wrong "it.")
>
>
> That sounds like a dare. Give me a few days and I'll have an
> "std::list<std::mutex>" populated with locked mutexes that were
> returned by value from functions (or functors!).
>
> Anyway I have this all working now, I have a function returning a
> locked mutex by value, and I have it placed inside a global
> std::optional. I've gotten stuff done on Sunday's before and all but
> today was an absolute corker.
>
> This code works entirely with 'std::function' instead of function
> pointers, so you can also use lambdas-with-captures or functor
> objects.
>
> Here it is up on GodBolt:
>
> https://godbolt.org/z/sfdTPq9GK
>
> I've done enough today, I'm going to the park.
>
> And here it is copy-pasted:
>
> #include <functional> // function
> #include <new> // placement 'new'
> #include <optional> // optional
> #include <type_traits> // remove_cvref_t
>
> // Providing forward-declaration of class
> // so that Invoker can make it a friend
> template<typename T>
> class PutRetvalInOptional;
>
> namespace detail {
>
> template<typename T, typename... Params>
> class Invoker {
>
> // The constructor of this class is private so that you can't just pull
> // one out of thin air. There is only one friend: PutRetvalInOptional<T>
>
> // An object of type 'Invoker<T,Params...>' is returned
> // by value from PutRetvalInOptional<T>::operator()<Params...>
>
> std::optional<T> *const popt = nullptr;
> std::function<T(Params...)> const &f;
>
> Invoker(std::optional<T> &argO, std::function<T(Params...)>
> const &argF) : popt(&argO), f(argF) {}
>
> public:
> void operator()(Params... args) /* deliberately non-const */
> {
> // 'std::optional<T>' inherits privately from
> 'std::_Optional_base<T>'
> // We need access to the base class to get/set '_M_value'
> and '_M_engaged'.
> auto &base =
> *static_cast<std::_Optional_base<T>*>(static_cast<void*>(popt));
>
> // The next four lines take advantage of a feature of the
> // System V x86_64 calling convention. The return value
> // can be placed as the first parameter (because both
> // use the RDI register).
> using F = std::function<T(Params...)>;
> using FuncPtr = void (*)(T*, F const *, Params...);
> auto const p = reinterpret_cast<FuncPtr>( &F::operator() );
> p( &base._M_payload._M_payload._M_value, &f,
> std::forward<Params>(args)... );
>
> // Lastly we need to manually set the 'has_value()' of the
> 'std::optional' to 'true'
> base._M_payload._M_engaged = true;
> }
>
> Invoker(Invoker &&) = delete;
> Invoker(Invoker const & ) = delete;
> Invoker &operator=(Invoker &&) = delete;
> Invoker &operator=(Invoker const & ) = delete;
>
> friend class PutRetvalInOptional<T>;
> };
> } // close namespace 'detail'
>
> template<typename T>
> class PutRetvalInOptional {
> std::optional<T> *const popt;
> public:
> PutRetvalInOptional(std::optional<T> &arg) : popt(&arg) {}
>
> template<typename... Params>
> detail::Invoker<T,Params...>
> operator()(std::function<T(Params...)> const &f)
> {
> return detail::Invoker<T,Params...>(*popt, f);
> }
>
> PutRetvalInOptional(PutRetvalInOptional &&) = delete;
> PutRetvalInOptional(PutRetvalInOptional const & ) = delete;
> PutRetvalInOptional &operator=(PutRetvalInOptional &&) = delete;
> PutRetvalInOptional &operator=(PutRetvalInOptional const & ) = delete;
> };
>
> extern "C" void InvokeFunctional(void *const p,
> std::function<void(void*)> const &f)
> {
> // I could have just written this function as
> // a few lines of assembler, but I didn't want
> // to have to worry about the mangling of:
> // std::function<void(void*)>::operator()
>
> if ( f ) f(p);
> }
>
> extern "C" void rvo_guaranteed_detail(void);
>
> __asm("rvo_guaranteed_detail: \n"
> ".intel_syntax noprefix \n"
> " push rdi \n" // save to restore later
> " push rdx \n" // save to restore later
> " call InvokeFunctional \n" // call 'create'
> " pop rdx \n" // restore after call
> " pop rdi \n" // restore after call
> " mov rsi, rdx \n" // Move 3rd argument to 2nd
> " call InvokeFunctional \n" // call 'manipulate'
> " ret \n"
> ".att_syntax");
>
> template<typename T>
> std::remove_cvref_t<T> rvo_guaranteed(
> std::function<void(std::remove_cvref_t<T>*)> create,
> std::function<void(std::remove_cvref_t<T>*)> manipulate )
> {
> auto const f = reinterpret_cast< decltype(&rvo_guaranteed<T>) >(
> rvo_guaranteed_detail );
> return f(create,manipulate);
> }
>
> // ===================== Here comes the test code:
> ==========================================
>
> #include <iostream> // cout
> using std::cout, std::endl;
>
> struct Mutex {
> // ---------- cannot move and cannot copy ----------
> Mutex(Mutex &&) = delete;
> Mutex(Mutex const & ) = delete;
> Mutex &operator=(Mutex &&) = delete;
> Mutex &operator=(Mutex const & ) = delete;
>
> Mutex(void) { cout << "construct\n"; }
> void lock(void) { cout << "lock\n" ; }
> void unlock(void) { cout << "unlock\n" ; }
> ~Mutex(void) { cout << "destroy\n" ; }
> };
>
> Mutex GiveMeLockedMutex(int const a, double const b, char const *const
> p) // dummy parameters
> {
> cout << "Args: " << a << " " << b << " " << p << endl;
>
> return rvo_guaranteed<Mutex>(
> [ ](Mutex *const p) { ::new(p) Mutex(); },
> [a](Mutex *const p) { if ( a % 2 ) p->lock(); } );
> }
>
> std::optional<Mutex> myglobal;
>
> int main(int const argc, char **const argv)
> {
> std::function< Mutex(int,double,char const *) > const stdfunc =
> GiveMeLockedMutex;
>
> PutRetvalInOptional(myglobal)(stdfunc)(argc,4.2,"Hello World");
>
> // GiveMeMutex has returned a locked mutex by value
> // ...and that mutex is now inside a global optional
>
> myglobal->unlock();
> }

Received on 2023-07-16 15:58:39