C++ Logo

std-proposals

Advanced search

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

From: Andrey Semashev <andrey.semashev_at_[hidden]>
Date: Fri, 2 May 2025 01:23:22 +0300
On 2 May 2025 01:00, Frederick Virchanza Gotham via Std-Proposals wrote:
> 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

If the function actually does throw (as it is allowed to, given that it
lacks noexcept specification), you're invoking UB, since your
noexcept-marked calling function now throws.

Received on 2025-05-01 22:23:27