【问题标题】:Can I take the address of a function defined in standard library?我可以获取标准库中定义的函数的地址吗?
【发布时间】:2021-10-13 23:19:00
【问题描述】:

考虑以下代码:

#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 的函数——一个在&lt;cctype&gt; 中,另一个在&lt;locale&gt; 中。引入了显式转换来选择所需的重载。)

【问题讨论】:

  • 哇,这是一个了不起的问题

标签: c++ language-lawyer c++-standard-library c++20 unspecified-behavior


【解决方案1】:

简答

没有。

说明

[namespace.std] 说:

F 表示标准库函数 ([global.functions])、标准库静态成员函数或标准库函数模板的实例化。 除非 F 被指定为可寻址函数,否则如果 C++ 程序显式或隐式地尝试形成一个指向F的指针。 [注意: 形成此类指针的可能方法包括应用一元 &amp; 运算符 ([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] 说:

头文件&lt;cctype&gt;的内容和含义与C标准库头文件&lt;ctype.h&gt;相同。

tolower 没有明确指定可寻址函数。

因此,这个 C++ 程序的行为是未指定的(可能是格式错误的),因为它试图形成一个指向 tolower 的指针,而该指针未被指定为可寻址函数。

结论

无法保证预期的输出。 事实上,代码甚至不能保证编译。


这也适用于成员函数。 [namespace.std] 没有明确提及这一点,但从 [member.functions] 可以看出,如果 C++ 程序试图获取声明的成员函数的地址,则其行为是未指定的(可能是格式错误的)在 C++ 标准库中。每[member.functions]/2

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

还有[expr.unary.op]/6:

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

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

(感谢comment 指出这一点!)

【讨论】:

  • 相关,interesting read(尽管本文没有涉及可寻址函数的概念)。
  • 有趣的是,C 标准采取了完全相反的观点:N1570 7.1.4 "...除非在详细说明中明确说明...允许采用图书馆功能。” (setjmp 是一个 C 库函数示例,其地址可能被占用,请参阅7.13p3。)
  • @L.F.这是否也适用于容器的成员函数?例如&amp;std::vector&lt;int&gt;::operator[]; 会错吗?我相信这没有包含在标准的上述引用中
  • 我想看看isn’t的定义是什么
  • @RinKaenbyou 是的。从技术上讲,我们需要将 tolower 包装在 lambda 中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多