请考虑以下代码:
#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
20 C++的预期产量是否得到保证?
(注意:有两个函数称为tolower
— 一个在<cctype>
中,另一个在<locale>
中。引入显式强制转换以选择所需的重载。
简答
不。
解释
[命名空间.std] 说:
让我们
F
表示标准库函数 ([global.functions])、标准库静态成员函数或标准库函数模板的实例化。除非F
被指定为可寻址函数,否则如果C++程序显式或隐式尝试形成指向F
的指针,则其行为是未指定的(可能格式不正确)。[注意:形成此类指针的可能方法包括应用一元&
运算符([expr.unary.op]),addressof
([specialized.addressof])或函数到指针的标准转换([conv.func])。 —尾注] 此外,如果 C++ 程序尝试形成对F
的引用,或者尝试形成指向成员的指针,指定标准库非静态成员函数 ([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++ 标准库中描述的非虚拟成员函数,实现可以声明一组不同的成员函数签名,前提是对成员函数的任何调用(将从本文档中描述的声明集中选择重载)的行为就像选择了该重载一样。[注意:例如,实现可以添加具有默认值的参数,或者将成员函数替换为具有两个或多个具有等效行为的成员函数的默认参数,或者为成员函数名称添加其他签名。
和 [expr.unary.op]/6:
重载函数的地址只能在唯一确定引用重载函数的哪个版本的上下文中获取(请参阅 [over.over])。[注意:由于上下文可能确定操作数是静态还是非静态成员函数,因此上下文也会影响表达式的类型是"指向函数的指针"还是"指向成员函数的指针"。
因此,如果程序显式或隐式尝试形成指向C++库中成员函数的指针,则程序的行为是未指定的(可能格式不正确)。
(感谢您的评论指出这一点!