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

Can I take the address of a function defined in standard library?

提问人:L. F. 提问时间:4/15/2019 最后编辑:L. F. 更新时间:7/13/2019 访问量:2804

问:

请考虑以下代码:

#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>

C 语言律师 ++标准库 C ++20 未指定行为

评论


答:

56赞 L. F. 4/15/2019 #1

简答

不。

解释

[namespace.std]说:

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

[expr.unary.op]/6

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

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

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

评论

4赞 lubgr 4/15/2019
相关、有趣的读物(尽管本文没有涉及可寻址函数的概念)。
1赞 zwol 4/16/2019
有趣的是,C 标准采取了完全相反的观点:N1570 7.1.4“......除非在详细说明中另有明确说明......它被允许采用库函数的地址。( 是 C 库函数的一个示例,其地址可能不被采用,参见 7.13p3setjmp
1赞 P.W 5/10/2019
@L.F.这是否也适用于容器的成员函数?例如会错吗?我相信这不包括在标准的上述引用中&std::vector<int>::operator[];
4赞 Caleth 5/10/2019
我想看看 an 的定义是什么isn’t
3赞 L. F. 10/5/2020
@RinKaenbyou是的。从技术上讲,我们需要包装在 lambda 中。tolower