Ad

Function Pointer Which Accepts Both With And Without Noexcept

I have some utility code that I've been using for years to safely call the ctype family of functions, it looks like this:

template<int (&F)(int)>
int safe_ctype(unsigned char c) {
    return F(c);
}

And is used like this:

int r = safe_ctype<std::isspace>(ch);

The idea being that it handles the need to cast the input int to an unsigned value for you in order to prevent undefined behavior. The specifics of this function is somewhat irrelivant though. Here's my question:

Now that in C++17 and later, noexcept is part of the type system, this is a compile error! Because all of the ctype functions are now noexcept.


EDIT: The above sentence is incorrect. the ctype family of functions are notnoexcept. I was however getting a compiler error in gcc < 11.2. https://godbolt.org/z/cTq94q5xE

The code works as expected (despite being technically not allowed due to these functions not being addressable) with the latest versions of all 3 major compilers.


I can of course change my function to look like this:

template<int (&F)(int) noexcept>
int safe_ctype(unsigned char c) noexcept {
    return F(c);
}

But now it doesn't work when compiled as C++11 or C++14. So I end up having to do something like this:

#if __cplusplus >= 201703L
template<int (&F)(int) noexcept>
int safe_ctype(unsigned char c) noexcept {
    return F(c);
}
#else
template<int (&F)(int)>
int safe_ctype(unsigned char c) {
    return F(c);
}
#endif

Which is getting increasingly complex for such a simple task. So is there a way to make the function pointer:

  1. valid for C++11 - C++20
  2. Accept both noexcept and non-noexcept when in C++17+

?

I tried doing something like this:

template<class F>
int safe_ctype(unsigned char c) noexcept {
    return F(c);
}

In the hopes that it would accept "anything", but sadly, no go.

Thoughts?

Ad

Answer

Now that in C++17 and later, noexcept is part of the type system, this is a compile error! Because all of the ctype functions are now noexcept.

It is not a compile error. Pointers to noexcept functions are implicitly convertible to pointers to potentially throwing functions, and thus the template accepting a pointer to potentially throwing functions works with both potentially throwing and noexcept functions. Only caveat is that the noexceptedness information is lost and might not be used for optimisation purposes.

Hence, the original solution satisfies both points 1. and 2.


Another problem pointed out in the comments is that the standard library functions (std::isspace) that you intend to use are not designated "addressable". Hence the behaviour of the program is unspecified (possibly ill-formed) due to forming a pointer to them.

To wrap such callable, you could use a lambda instead of a function pointer. But that makes the template itself obsolete since you can change the argument type of the lambda directly:

auto safe_isspace = [](unsigned char c){ return std::isspace(c); };
int r = safe_isspace(ch);

Though we no longer need to pass this into a template, so the same can be achieved with a plain function:

int // or bool?
safe_isspace(unsigned char c) noexcept // ...

Since this involves a bit of identical boilerplate for multiple functions, this is a good candidate for meta-programming.

Ad
source: stackoverflow.com
Ad