请考虑以下代码:
#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中的预期输出是否有保证?
(注意:有两个函数称为tolower
- 一个在<cctype>
,另一个在<locale>
。引入显式转换以选择所需的重载。)
没有。
让
F
表示标准库函数([global.functions]),标准库静态成员函数或标准库函数模板的实例化。除非将F
指定为可寻址函数,否则如果C ++程序明确或隐式地尝试形成指向F
的指针,则其行为未指定(可能是格式不正确)。 [注意:形成这样的指针的可能方法包括应用一元&
算子([expr.unary.op]),addressof
([specialized.addressof])或函数到指针标准转换([conv.func])。 - 结束语]此外,如果C ++程序试图形成对F
的引用,或者它试图形成指向标准库非静态成员的指针成员,那么它的行为是未指定的(可能是格式错误的) function([member.functions])或标准库成员函数模板的实例化。
考虑到这一点,让我们检查两次调用std::invoke
。
std::invoke(std::boolalpha, std::cout);
在这里,我们试图形成一个指向std::boolalpha
的指针。幸运的是,[fmtflags.manip]节省了一天:
本子条款中指定的每个函数都是指定的可寻址函数([namespace.std])。
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]说:
标题
<cctype>
的内容和含义与C标准库标题<ctype.h>
相同。
tolower
没有明确指定可寻址函数。
因此,此C ++程序的行为未指定(可能是格式错误),因为它试图形成指向tolower
的指针,该指针未指定为可寻址函数。
预计产量无法保证。实际上,代码甚至不能保证编译。
这也适用于成员函数。 [namespace.std]没有明确提到这一点,但是从[member.functions]可以看出,如果C ++程序试图获取声明的成员函数的地址,那么它的行为是未指定的(可能是格式错误的)在C ++标准库中。每[member.functions]/2:
对于C ++标准库中描述的非虚拟成员函数,实现可以声明一组不同的成员函数签名,前提是对成员函数的任何调用将从本文档中描述的声明集中选择重载,其行为与如果选择了那个重载[注意:例如,实现可以使用默认值添加参数,或者使用具有两个或更多具有等效行为的成员函数的默认参数替换成员函数,或者为成员函数名称添加其他签名。 - 结束说明]
重载函数的地址只能在唯一确定引用过载函数版本的上下文中进行(参见[over.over])。 [注意:由于上下文可能确定操作数是静态成员函数还是非静态成员函数,因此上下文也会影响表达式是否具有“指向函数的指针”或“指向成员函数的指针”。 - 结束说明]
因此,如果程序显式或隐式地尝试在C ++库中形成指向成员函数的指针,则程序的行为是未指定的(可能是格式错误的)。
(感谢comment指出这一点!)