提问人:L. F. 提问时间:4/15/2019 最后编辑:L. F. 更新时间:7/13/2019 访问量:2804
我可以获取标准库中定义的函数的地址吗?
Can I take the address of a function defined in standard library?
问:
请考虑以下代码:
#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";
}
在这里,标记了两个调用以供将来参考。
预期输出为:std::invoke
a
C++20 是否保证了预期的输出?
(注意:有两个函数称为 — 一个是 in,另一个是 。引入显式强制转换以选择所需的重载。tolower
<cctype>
<locale>
答:
简答
不。
解释
设
F
表示标准库函数 ([global.functions])、标准库静态成员函数或标准库函数模板的实例化。除非将 F 指定为可寻址函数,否则如果 C++ 程序显式或隐式地尝试形成指向
F
的指针,则该程序的行为未指定(可能格式不正确)。[注:形成此类指针的可能方法包括应用一元运算符 ([expr.unary.op])、([specialized.addressof]) 或函数到指针的标准转换 ([conv.func])。 — 尾注 ] 此外,如果 C++ 程序试图形成对F
的引用,或者如果它试图形成指向成员的指针,指定标准库非静态成员函数 ([member.functions]) 或标准库成员函数模板的实例化,则该程序的行为是未指定的(可能格式不正确)。&
addressof
考虑到这一点,让我们检查一下对 .std::invoke
第一次通话
std::invoke(std::boolalpha, std::cout);
在这里,我们试图形成一个指向 的指针。幸运的是,[fmtflags.manip] 挽救了这一天:std::boolalpha
此子句中指定的每个函数都是一个指定的可寻址函数 ([namespace.std])。
and 是本子句中指定的函数。
因此,这条线的格式良好,等效于:boolalpha
std::cout.setf(std::ios_base::boolalpha);
但这是为什么呢?好吧,以下代码是必要的:
std::cout << std::boolalpha;
第二次通话
std::cout << std::invoke(static_cast<ctype_func>(std::tolower), 'A') << "\n";
不幸的是,[cctype.syn] 说:
头文件的内容和含义与C标准库头文件相同。
<cctype>
<ctype.h>
没有任何地方被明确指定为可寻址函数。tolower
因此,此 C++ 程序的行为未指定(可能格式不正确),因为它试图形成指向 的指针,该指针未被指定为可寻址函数。tolower
结论
不能保证预期的输出。 事实上,代码甚至不能保证编译。
这也适用于成员函数。 [namespace.std] 没有明确提到这一点,但从 [member.functions] 可以看出,如果 C++ 程序尝试获取 C++ 标准库中声明的成员函数的地址,则该程序的行为是未指定的(可能格式不正确)。根据 [member.functions]/2:
对于 C++ 标准库中描述的非虚拟成员函数,实现可以声明一组不同的成员函数签名,前提是对成员函数的任何调用都将从本文档中描述的声明集中选择重载,其行为就像选择了该重载一样。[注意:例如,实现可以添加具有默认值的参数,或者将具有默认参数的成员函数替换为具有等效行为的两个或多个成员函数,或者为成员函数名称添加其他签名。
重载函数的地址只能在唯一确定引用的重载函数版本的上下文中获取(请参阅 [over.over])。[ 注意:由于上下文可能确定操作数是静态成员函数还是非静态成员函数,因此上下文还会影响表达式的类型是“指向函数的指针”还是“指向成员函数的指针”。
因此,如果程序显式或隐式尝试形成指向 C++ 库中成员函数的指针,则程序的行为未指定(可能格式不正确)。
(感谢您指出这一点的评论!
评论
setjmp
&std::vector<int>::operator[];
isn’t
tolower
评论