根据 cppreference.com,C++20 为标准库函数引入了“寻址限制”:
寻址限制
如果 C++ 程序显式或隐式地尝试形成指针、引用(对于自由函数和静态成员函数)或指向成员的指针(对于非静态成员函数),则该程序的行为是未指定的(可能是错误的)到标准库函数或标准库函数模板的实例化,除非它被指定为可寻址函数(见下文)。
以下代码在 C++17 中定义明确,但会导致未指定的行为,并且自 C++20 以来可能无法编译:
#include <cmath> #include <memory> int main() { auto fptr0 = &std::betaf; // by unary operator& auto fptr1 = std::addressof(std::betal); // by std::addressof auto fptr2 = std::riemann_zetaf; // by function-to-pointer implicit conversion auto &fref = std::riemann_zetal; // forming a reference }
为什么要介绍这个?
特别是关于语言的向后兼容性,这似乎是一个破坏了很多现有代码的变化。这提供了什么好处值得这样的突破性变化?
它从来都不是“明确定义的”,它只是碰巧适用于您的/某些/大多数实现。 (这里的CppReference是错误的)
隐藏在标准库部分的介绍中的是对实施者必须提供的内容的描述。对于(比如说)
std:: riemann_zetal
,需要的是有一个可以使用函数调用语法调用的“东西”,它接受一个单独的float
参数,并返回可以转换为float
的东西。
所以,这是一个完全合法的实现:
template <typename FP>
FP ZETA(FP f, int flags = 0) { /* code here */ }
#define riemann_zetaf(x) ZETA<float>(x, 23)
但是,你不能拿
riemann_zetaf
的地址,因为没有这样的野兽。
请注意,即使没有宏,您也无法保证
addressof(riemann_zetal)
会返回一个只接受一个参数的函数。你唯一的保证是你可以用一个参数调用它。
如果您需要可以取地址的东西,请自行定义:
float riemann_zetal (float x) { return std::riemann_zetal(x); }
然后取那个的地址。
他们应该估计没有相当数量的生产软件真正使用现在被破坏的代码,这似乎是合理的;因为主流软件公司都投了赞成票。 这种对用户代码的限制意味着对库代码的更多解放。在标准的未来修订中,实施者可能会使用替代构造而不是普通函数。从具有推导/默认参数的模板到功能对象或平台特定的非标准扩展或今天不可用的未来核心功能,可能性是无限的。他们可能想将某些功能重新定义为协程或 lambda。从 C++20 开始,这些实用程序唯一可靠的部分是它们的函数/成员调用签名。