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
>
> 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