Date: Sat, 15 Jul 2023 13:16:36 -0300
It would be terrible if functions are suddenly allowed to return bomb
objects that you cannot touch under some condition that is only known if
you read the implementation of the function.
I think allowing that would be a bad move for the language in terms of
readability and ergonomics.
If you want to return something that may or may not be there, it's an
std::optional.
If you know the condition by construction, that's an union.
It would be very clear to the reader the object they got may not be there.
Breno G.
Em sáb., 15 de jul. de 2023 12:55, Frederick Virchanza Gotham via
Std-Proposals <std-proposals_at_[hidden]> escreveu:
> 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
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
objects that you cannot touch under some condition that is only known if
you read the implementation of the function.
I think allowing that would be a bad move for the language in terms of
readability and ergonomics.
If you want to return something that may or may not be there, it's an
std::optional.
If you know the condition by construction, that's an union.
It would be very clear to the reader the object they got may not be there.
Breno G.
Em sáb., 15 de jul. de 2023 12:55, Frederick Virchanza Gotham via
Std-Proposals <std-proposals_at_[hidden]> escreveu:
> 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
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
Received on 2023-07-15 16:16:49