41

考虑以下代码:

#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——一个 in<cctype>和另一个 in <locale>。显式转换被引入来选择所需的重载。)

4

1 回答 1

48

简短的回答

不。

解释

[namespace.std]说:

LetF表示标准库函数 ( [global.functions] )、标准库静态成员函数或标准库函数模板的实例化。 除非F被指定为可寻址函数,否则如果 C++ 程序显式或隐式尝试形成指向F. [注意:形成此类指针的可能方法包括应用一元运算&符([expr.unary.op])、addressof[specialized.addressof])或函数到指针的标准转换([conv.func])。— <em>end note ] 此外,如果 C++ 程序试图形成对的引用,F或者如果它试图形成一个指向成员的指针来指定标准库非静态成员函数 ( [member.functions] ) 或标准库成员函数模板的实例化。

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

第一次通话

std::invoke(std::boolalpha, std::cout);

在这里,我们试图形成一个指向std::boolalpha. 幸运的是,[fmtflags.manip]拯救了这一天:

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

Andboolalpha是本小节中指定的函数。因此,这条线是格式良好的,相当于:

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++ 标准库中描述的非虚拟成员函数,实现可以声明一组不同的成员函数签名,前提是对从本文档中描述的声明集中选择重载的成员函数的任何调用都表现为如果选择了该重载。[ <em>注意:例如,实现可以添加具有默认值的参数,或者将具有默认参数的成员函数替换为具有等效行为的两个或多个成员函数,或者为成员函数名称添加额外的签名。— <em>结束注释]

[expr.unary.op]/6

重载函数的地址只能在唯一确定引用哪个版本的重载函数的上下文中获取(参见 [over.over])。[ <em>注意:由于上下文可能决定操作数是静态还是非静态成员函数,上下文也会影响表达式的类型是“函数指针”还是“成员函数指针”。— <em>结束注释]

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

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

于 2019-04-15T10:20:12.123 回答