接受有和没有 noexcept 的函数指针

Function pointer which accepts both with and without noexcept

我有一些实用程序代码,我多年来一直在使用它来安全地调用 ctype 系列函数,它看起来像这样:

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

并且是这样使用的:

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

想法是它处理将输入 int 转换为无符号值的需要,以防止出现未定义的行为。不过,此功能的细节有些无关紧要。这是我的问题:

既然在C++17及以后的版本中,noexcept是类型系统的一部分,这是一个编译错误!因为所有的 ctype 函数现在都是 noexcept.


EDIT: 上面这句话是不正确的。 ctype 函数族是 not noexcept。然而,我在 gcc < 11.2 中遇到编译器错误。 https://godbolt.org/z/cTq94q5xE

代码在所有 3 个主要编译器的最新版本中按预期工作(尽管由于这些函数不可寻址而在技术上不允许)。


我当然可以将函数更改为如下所示:

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

但现在编译为 C++11 或 C++14 时它不起作用。所以我最终不得不做这样的事情:

#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

如此简单的任务却变得越来越复杂。那么有没有办法让函数指针:

  1. 适用于 C++11 - C++20
  2. 在 C++17+ 中接受 noexcept 和非 noexcept

?

我试过这样做:

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

希望它能接受“任何东西”,但遗憾的是,没有接受。

想法?

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.

不是编译错误。指向 noexcept 函数的指针可以隐式转换为指向潜在抛出函数的指针,因此接受指向潜在抛出函数的指针的模板适用于潜在抛出函数和 noexcept 函数。唯一需要注意的是,noexceptedness 信息会丢失并且可能不会用于优化目的。

因此,原解同时满足第1点和第2点


评论中指出的另一个问题是您打算使用的标准库函数(std::isspace)未指定为“可寻址”。因此,由于形成指向它们的指针,程序的行为是未指定的(可能 ill-formed)。

要包装此类可调用对象,您可以使用 lambda 而不是函数指针。但这会使模板本身过时,因为您可以直接更改 lambda 的参数类型:

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

虽然我们不再需要将其传递到模板中,但可以使用普通函数实现相同的目的:

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

由于这涉及多个函数的一些相同样板,因此这是 meta-programming 的一个很好的候选者。

Because all of the ctype functions are now noexcept.

这是不正确的。 C++17 没有将 noexcept 添加到通过 C++ c* headers 访问的任何 C-library 函数。你可以see here所有的C++函数声明都不包含noexcept。并且不允许使用标准库实现非 noexcept 函数 noexcept.

其次,即使是noexcept,一个noexcept函数指针也可以转换成一个throwing函数指针(但反过来不行)。所以 your code compiles.

但最重要的是,C++20 清楚地表明,您 不允许 获取任何 C++ 标准库函数的函数指针,除非明确声明它是“可寻址的” ”。而且C++标准库中可寻址的函数非常少

因此在 C++20 中,您的代码将产生 UB。如果您希望您的代码适用于所有语言版本,您只需为 cctype 函数编写包装器。