C++ Logo

std-proposals

Advanced search

Re: [std-proposals] std::dummy_prvalue -- Kind of, sort of, like a std::declval that you can evaluate

From: Breno Guimarães <brenorg_at_[hidden]>
Date: Sat, 15 Jul 2023 12:48:16 -0300
Also, how would the compiler know that it should not call the destructor of
the object in Monkey?

Em sáb., 15 de jul. de 2023 12:07, Breno Guimarães <brenorg_at_[hidden]>
escreveu:

> I'm on my phone so I can't test, but have you tried returning a union? Not
> sure if you could get what you want, but it could work.
>
> Breno G.
>
> Em sáb., 15 de jul. de 2023 10:03, Frederick Virchanza Gotham via
> Std-Proposals <std-proposals_at_[hidden]> escreveu:
>
>> Let's say I have a function as follows that might throw an exception:
>>
>> class MyClass { ........ };
>>
>> MyClass Func(int, double, char*) noexcept(false);
>>
>> I decide to wrap this function in a function that never throws:
>>
>> thread_local std::exception_ptr e;
>>
>> MyClass FuncNoThrow(int a, double b, char *c) noexcept
>> {
>> try { return Func(a,b,c); }
>> catch(...) { e = std::current_exception(); }
>>
>> return {};
>> }
>>
>> That return statement at the end is really just to suppress a compiler
>> warning and also to avoid what strictly-speaking is 'undefined
>> behaviour' -- you're not allowed to fall off the end of a
>> non-void-returning function.
>>
>> In the System V x86_64 calling convention, a return statement can do
>> three different things depending on how big the return value is:
>> (1) If the return value <= 8 bytes, it just sets the RAX register
>> (2) If 8 bytes < return value <= 16 bytes, it sets both RAX and RDX
>> (3) if return value > 16 bytes, it stores the return value in the
>> memory pointed to by RDI
>>
>> So if you don't have a return statement in a non-void-returning
>> function, then there won't be any real drama so long as you don't try
>> to access the return value in the caller function (because you're not
>> accessing RAX, RDX, or memory pointed to by RDI). Nonetheless, the C++
>> Standard says that it's undefined behaviour irrespective of whether
>> you try to do anything with the return value. (I can see however see
>> how there would be a problem if MyClass had a nontrivial destructor).
>>
>> FuncNoThrow could be utilised as follows:
>>
>> void Monkey(void)
>> {
>> e = nullptr;
>> auto retval = FuncNoThrow(5, 6.2, nullptr);
>> if ( nullptr != e ) std::rethrow_exception(e);
>> cout << retval << endl;
>> }
>>
>> This will all work fine so long as 'MyClass' has a default
>> constructor. If it doesn't, then the "return {};" won't compile. If
>> there's no default constructor, we could try something like:
>>
>> MyClass FuncNoThrow(int a, double b, char *c) noexcept
>> {
>> try { return FunctionName(a,b,c); }
>> catch(...) { e = std::current_exception(); }
>>
>> alignas(MyClass) static char dummy[sizeof(MyClass)] = {};
>> return *static_cast<MyClass*>(static_cast<void*>(dummy));
>> }
>>
>> This would work fine unless the 'move constructor' for MyClass gets
>> invoked and tries to copy data to a nullptr. What I really want here
>> is the mandatory elision of copy/move operations provided by "return
>> value optimisation", but unfortunately it's not provided in this case.
>> Somehow I need to get a PRvalue in order for elision to be mandatory.
>> So maybe something along the lines of:
>>
>> void dummy_prvalue_detail(void) {}
>>
>> template<typename T> requires std::is_trivially_destructible_v<
>> std::remove_cvref_t<T> >
>> std::remove_cvref_t<T> dummy_prvalue(void) noexcept
>> {
>> typedef std::remove_cvref_t<T> TT;
>> TT (*const funcptr)(void) = reinterpret_cast<TT(*)(void)>(
>> dummy_prvalue_detail );
>> return funcptr(); // guaranteed elision of move/copy operations
>> here
>> }
>>
>> What I really need here is something along the lines of 'std::declval'
>> but:
>> (1) It can be evaluated
>> (2) It gives you a PRvalue (ensuring elision for return value
>> optimisation)
>> (3) It is safe and its behaviour is well-defined so long as you don't
>> try to access the non-existent object
>> (4) It will reject types that have a non-trivial destructor
>>
>> An alternative to this would be to allow non-void-returning functions
>> to just fall off the end, so long as the return value is not used in
>> the caller function, meaning we'd just have:
>>
>> MyClass FuncNoThrow(int a, double b, char *c) noexcept
>> {
>> try { return Func(a,b,c); }
>> catch(...) { e = std::current_exception(); }
>>
>> // falling off the end here is fine so long
>> // as MyClass has a trivial destructor, and
>> // so long as you don't use the return value
>> }
>>
>> So maybe C++26 could either:
>> (a) Allow the falling off the end of non-void-returning functions so
>> long as the return type is trivially destructible.
>> or:
>> (b) Have in the standard library a "dummy_prvalue" template function
>> such as what I've written, with well-defined behaviour so long as you
>> don't try to access the return value.
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>

Received on 2023-07-15 15:48:31