Home > other >  Function pointer which accepts both with and without noexcept
Function pointer which accepts both with and without noexcept

Time:02-03

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 not noexcept. 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?

CodePudding user response:

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.

CodePudding user response:

Because all of the ctype functions are now noexcept.

This is untrue. C 17 did not add noexcept to any C-library functions accessed through the C c* headers. You can see here that all of the C function declarations do not contain noexcept. And a standard library implementation is not allowed to make non-noexcept functions noexcept.

Secondly, even if it were noexcept, a noexcept function pointer can be converted into a throwing function pointer (but not the other way around). So your code compiles.

But most importantly, C 20 makes it clear that you are not allowed to get function pointers for any C standard library function unless it is specifically stated to be "addressable". And there are very few addressable functions in the C standard library.

So in C 20, your code will yield UB. You're just going to have to write wrappers for the cctype functions if you want your code to work across all language versions.

  •  Tags:  
  • Related