Date: Sun, 16 Jul 2023 16:39:38 +0100
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.
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();
}
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.
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:39:47