C++ Logo

std-proposals

Advanced search

Re: [std-proposals] noexcept has gotten a bit hairy -- I want a compiler error

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Thu, 1 May 2025 23:00:07 +0100
On Tue, Apr 29, 2025 at 10:38 AM Frederick Virchanza Gotham wrote:
>
> template<typename Lambda, typename... Params>
> decltype(auto) wont_throw(Lambda &&f, Params&&... args) noexcept
> {
> auto const mfp = &Lambda::template operator()<Params...>;
> using FuncPtrNoExcept = typename
> make_memfuncptr_noexcept<decltype(mfp)>::type;
> auto const mfpne =
> *static_cast<FuncPtrNoExcept*>(static_cast<void const*>(&mfp));
> return (f.*mfpne)( forward<Params>(args)... );
> }


Just to expand on this a bit. Let's start out with a declaration for a
function that's buried in a 3rd party library, perhaps in a DLL or SO
file:

    extern void SomeOtherFunc(void) noexcept(false);

Next let's write a simple function in our own program as follows:

    int Func(void)
    {
        SomeOtherFunc();
        return 7;
    }

Using clang up on GodBolt, I get the following assembler for 'Func':

Func():
    push %rax
    call 6 <Func()+0x6>
        R_X86_64_PLT32 SomeOtherFunc()-0x4
    mov $0x7,%eax
    pop %rcx
    ret

Now let's make a tiny alteration. Let's mark 'Func' as 'noexcept' and
see how the assembler changes:

  Func():
     push %rax
     call 6 <Func()+0x6>
        R_X86_64_PLT32 SomeOtherFunc()-0x4
     mov $0x7,%eax
     pop %rcx
     ret
     mov %rax,%rdi
     call 15 <Func()+0x15>
        R_X86_64_PLT32 __clang_call_terminate-0x4

You can see that two more instructions have been tagged onto the end:

     mov %rax,%rdi
     call 15 <Func()+0x15>
        R_X86_64_PLT32 __clang_call_terminate-0x4

So let's now try use the 'WONT_THROW' macro. We'll change 'Func' as follows:

    int Func(void) noexcept
    {
        WONT_THROW( SomeOtherFunc );
        return 7;
    }

and now Godbolt shows me the following assembler for 'Func':

  Func():
     push %rax
     call 6 <Func()+0x6>
        R_X86_64_PLT32 SomeOtherFunc()-0x4
    mov $0x7,%eax
    pop %rcx
    ret

So we have achieved our desired result -- the extra instructions to
invoke 'std::terminate' have not been added.

Here's how the entire source file now looks:

#include <utility> // forward

namespace std {

    template<typename T>
    struct make_memfuncptr_noexcept {
        static_assert( nullptr == "The type T must be a const member
function pointer" );
    };

    template<typename R, typename Class, typename... Params>
    struct make_memfuncptr_noexcept< R(Class::* const)(Params...)
const noexcept(false) > {
        using type = R (Class::* const)(Params...) const noexcept;
    };

    template<typename Lambda, typename... Params>
    decltype(auto) wont_throw(Lambda &&f, Params&&... args) noexcept
    {
        constexpr auto mfp = &Lambda::template operator()<Params...>;
        using FuncPtrNoExcept = typename
make_memfuncptr_noexcept<decltype(mfp)>::type;
        auto const mfpne =
*static_cast<FuncPtrNoExcept*>(static_cast<void const*>(&mfp));
        return (f.*mfpne)( forward<Params>(args)... );
    }
}

#define WONT_THROW(f, ...)
           \
    std::wont_throw(
           \
        [&](auto&&... args) noexcept(false) -> decltype(auto)
           \
        {
           \
            return (f)(std::forward<decltype(args)>(args)...);
           \
        }
           \
        __VA_OPT__(,) __VA_ARGS__ )

extern void SomeOtherFunc(void) noexcept(false);

int Func(void) noexcept
{
    WONT_THROW( SomeOtherFunc );
    return 7;
}

And if we run this through the preprocessor, here's what comes out of
the preprocessor for the body of 'Func':

int Func(void) noexcept
{
    std::wont_throw(
        [&](auto&&... args) noexcept(false) -> decltype(auto)
        {
            return (SomeOtherFunc)(std::forward<decltype(args)>(args)...);
        }
    );
    return 7;
}

So if there were to be something like this in the C++29, then I think
there are three possibilities:
(1) Do it the way I've done it (i.e. using a preprocessor macro)
(2) Introduce a new way of using an overloaded function as a template parameter
(3) Provide compiler support so that "std::no_throw" can magically
accept an overloaded function such as strchr

Received on 2025-05-01 22:00:21