我可以使用标准库中定义的函数的地址吗?

L. *_* F. 27 c++ c++-standard-library language-lawyer unspecified-behavior c++20

考虑以下代码:

#include <cctype>
#include <functional>
#include <iostream>

int main()
{
    std::invoke(std::boolalpha, std::cout); // #1

    using ctype_func = int(*)(int);
    char c = std::invoke(static_cast<ctype_func>(std::tolower), 'A'); // #2
    std::cout << c << "\n";
}
Run Code Online (Sandbox Code Playgroud)

在此,对的两个调用std::invoke已标记为将来参考。预期的输出是:

a
Run Code Online (Sandbox Code Playgroud)

在C ++ 20中可以保证预期的输出吗?

(注意:有两个函数tolower,一个称为in <cctype>,另一个称为in <locale>。引入了显式强制转换以选择所需的重载。)

L. *_* F. 31

简短答案

没有。

说明

[namespace.std]说:

让我们F表示标准库函数([global.functions]),标准库静态成员函数或标准库函数模板的实例化。 除非F被指定为可寻址函数,否则C ++程序的行为(如果它显式或隐式地尝试形成指向的指针)是不确定的(可能是不正确的)F [ 注意:形成此类指针的可能方法包括应用一元运算&符([expr.unary.op]),addressof[specialized.addressof])或函数到指针的标准转换([conv.func])。-?尾注此外,一个C的行为++程序是未指定的(可能是非法的构造),如果它试图以形成一个参考F,或者如果它试图以形成指针到部件指定一个标准库非静态成员函数([构件.functions])或标准库成员函数模板的实例化。

考虑到这一点,让我们检查对的两个调用std::invoke

第一次电话

std::invoke(std::boolalpha, std::cout);
Run Code Online (Sandbox Code Playgroud)

在这里,我们试图形成指向的指针std::boolalpha。幸运的是,[fmtflags.manip]节省了一天:

本节中指定的每个函数都是指定的可寻址函数([namespace.std])。

boolalpha在本节中定义的功能。因此,此行格式正确,等效于:

std::cout.setf(std::ios_base::boolalpha);
Run Code Online (Sandbox Code Playgroud)

但是为什么呢?好吧,下面的代码是必要的:

std::cout << std::boolalpha;
Run Code Online (Sandbox Code Playgroud)

第二次通话

std::cout << std::invoke(static_cast<ctype_func>(std::tolower), 'A') << "\n";
Run Code Online (Sandbox Code Playgroud)

不幸的是,[cctype.syn]说:

标头的内容和含义<cctype>与C标准库标头相同<ctype.h>

没有在任何地方tolower明确指定可寻址功能。

因此,此C ++程序的行为是不确定的(可能是格式错误的),因为它试图形成一个指向的指针tolower,该指针未指定为可寻址函数。

结论

无法保证预期的输出。实际上,甚至不保证代码可以编译。


这也适用于成员函数。[namespace.std]并未明确提及这一点,但是从[member.functions]可以看出,如果C ++程序尝试获取已声明的成员函数的地址,则该行为是不确定的(可能是格式错误的)在C ++标准库中。每[member.functions] / 2

对于C ++标准库中描述的非虚拟成员函数,实现可以声明一组不同的成员函数签名,前提是对该成员函数的任何调用都会从本文档中所述的声明集中选择重载,其行为如下:如果选择了该过载。[?注意:例如,实现可以添加具有默认值的参数,或者用具有相同行为的两个或多个成员函数将成员函数替换为具有默认参数的成员函数,或者为成员函数名称添加其他签名。-?尾注?]

[expr.unary.op] / 6

重载函数的地址只能在唯一确定重载函数的版本的上下文中使用(请参见[over.over])。[?注意:由于上下文可能确定操作数是静态还是非静态成员函数,因此上下文也会影响表达式的类型是“函数指针”还是“成员函数指针”。-?尾注?]

因此,如果程序的行为明确或隐式地试图形成指向C ++库中成员函数的指针,则该行为是不确定的(可能是格式错误的)。

(谢谢你的评论指出这一点!)

  • 相关的,[有趣的阅读](https://akrzemi1.wordpress.com/2018/07/07/functions-in-std/)(尽管本文并未涉及可寻址函数的概念)。 (3认同)
  • 我想看看“不是”的定义是什么 (2认同)
  • @RinKaenbyou 是的。从技术上讲,我们需要将 `tolower` 包装在 lambda 中。 (2认同)