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: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Sat, 15 Jul 2023 17:01:54 +0100
That's why I put a constraint on the template to make sure the
destructor was trivial.

If you're asking me how would I get this to work properly with a class
that has a non-trivial destructor ..... well ....... there would need
to be language support for that, something like:

void Monkey(void)
 {
 e = nullptr;
 auto retval = FuncNoThrow(5, 6.2, nullptr);
 if ( nullptr != e )
  {
      __dont_destroy(retval);
      std::rethrow_exception(e);
   }
   cout << retval << endl;
 }




On Sat, Jul 15, 2023 at 4:48 PM Breno Guimarães <brenorg_at_[hidden]> wrote:
>
> 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:55:42