为什么我们需要将函数标记为constexpr?

Pot*_*ter 45 c++ constexpr c++11

C++ 11允许使用说明constexpr符声明的函数用于常量表达式,例如模板参数.对于允许的内容有严格的要求constexpr; 本质上这样的函数只包含一个子表达式,而不包含任何其他子表达式.(编辑:这在C++ 14中是放松的,但问题是.)

为什么要求关键字?得到了什么?

它确实有助于揭示接口的意图,但它不会通过保证函数在常量表达式中可用来验证该意图.编写constexpr函数后,程序员必须仍然:

  1. 编写测试用例或以其他方式确保它实际用于常量表达式.
  2. 记录在常量表达式上下文中哪些参数值有效.

与揭示意图相反,装饰功能constexpr可能会增加错误的安全感,因为在忽略中心语义约束的同时检查了切向句法约束.


简而言之:如果constexpr函数声明仅仅是可选的,那么对语言会有什么不良影响吗?或者对任何有效的程序都会有任何影响吗?

Ton*_*roy 38

防止客户端代码期望超出您的承诺

假设我正在编写一个库并且在那里有一个当前返回常量的函数:

awesome_lib.h:

inline int f() { return 4; }
Run Code Online (Sandbox Code Playgroud)

如果constexpr不是必需的话,作为客户端代码的作者,您可能会离开并执行以下操作:

client_app.cpp:

#include <awesome_lib.h>
int my_array[f()];
Run Code Online (Sandbox Code Playgroud)

然后我应该改变f()说从配置文件返回值,你的客户端代码会破坏,但我不知道我冒着破坏你的代码的风险.实际上,可能只有当你遇到一些生产问题并重新编译时才发现这个额外的问题会让你的重建受挫.

只改变实施f(),我会有效地改变可能被作出的使用界面.

相反,C++ 11以后提供,constexpr所以我可以表示客户端代码可以合理期望函数剩余a constexpr,并使用它本身. 我知道并认可这种用法是我界面的一部分. 正如在C++ 03中一样,编译器继续保证客户端代码不会依赖于其他非constexpr函数来构建,以防止上面的"不需要/未知的依赖"场景; 这不仅仅是文档 - 它是编译时的执行.

值得注意的是,这延续了C++的趋势,即为预处理器宏的传统用途提供更好的替代方案(考虑#define F 4,以及客户端程序员如何知道lib程序员是否认为公平游戏要改变#define F config["f"]),以及他们众所周知的"邪恶"等因为在语言的命名空间/类范围系统之外.

为什么没有"明显"永不变量函数的诊断?

我认为这里的混淆是由于constexpr没有主动确保有任何一组参数,其结果实际上是编译时const:相反,它要求程序员对此负责(否则标准中的§7.1.5/ 5)认为程序格式错误,但不要求编译器发出诊断信息).是的,这是不幸的,但它并没有消除上述效用constexpr.

所以,也许问题不应该是"有什么意义constexpr",而是"为什么我可以编译一个constexpr永远不能实际返回const值的函数?".答:因为需要进行详尽的分支分析,这可能涉及任意数量的组合.编译时和/或内存(甚至超出任何可以想象的硬件的能力)诊断成本可能过高.此外,即使在实际必须准确诊断此类情况时,编译器编写者(已经在C++ 11实现中已经足够忙)也是一种全新的蠕虫病毒.还会对程序产生影响,例如constexpr在执行验证时需要在函数内调用的函数的定义(以及函数调用的函数等).

同时,缺少constexpr仍然禁止使用作为一个常量的值:在严格是对sans- constexpr侧.如上所述,这很有用.

与非`constst`成员函数进行比较

  • constexpr防止int x[f()]缺乏const预防const X x; x.f();- 他们都确保客户端代码不会硬编码不需要的依赖

  • 在这两种情况下,您都不希望编译器const[expr]自动确定-ness:

    • 你不希望客户端代码调用const对象上的成员函数,当你已经预见到函数将演变为修改可观察值,打破客户端代码

    • 如果您已经预期稍后在运行时确定它,则不希望将值用作模板参数或数组维度

  • 它们的不同之处在于编译器强制const使用const成员函数中的其他成员,但不强制执行编译时常量结果constexpr(由于实际的编译器限制)

  • 是的,这是关于"揭露意图".但意图并不保证.您仍然可以从配置文件中获取值而不删除`constexpr`.这些意图问题最终留给文件. (8认同)
  • 重新编辑...要明确问题,你可以定义`constexpr int f(){return fetch_config("bob's high score"); ``并且编译器在`my_array [f()];`中使用之前不会抱怨.因此,当你编写`constexpr`时,你还必须同时记录*当*它是一个常量,而`static_assert`则文档是正确的.完成所有这些后,`constexpr`是多余的,`static_assert`是用户应该*真正*正在查看的内联文档. (4认同)
  • 不需要诊断,因为在所有可能的情况下发射诊断将需要解决停止问题. (3认同)
  • @SebastianRedl:虽然这是真的,但这并没有改变编译器不确认有一组参数导致编译时const函数的情况.`constexpr`不保证它,因为编译器_cannot_保证它.规范有该规则,但不能强制执行,因此不是实际的保证. (3认同)
  • @Potatoswatter:但这不仅仅是文档,因为它是在需要 const 表达式的客户端代码中强制执行的:它们无法构建与您的“记录的”意图不一致的客户端使用情况。我并不是说这很彻底,但它仍然很有价值。一般来说,让编译器在查看客户端使用之前检查“constexpr”实现会给程序员带来新的限制 - 例如,在“constexpr”函数内部调用的函数必须定义,而不仅仅是在检查完成之前声明。 (2认同)
  • @Potatoswatter:一个静态断言可以帮助保证一个函数返回一个const结果,这是作者的意图,但是如果你正在编写一个函数而不是必须返回一个const值,但现在恰好发生,你不是想要编写静态断言的代码,客户端代码仍然可以耦合到它.`constexpr`在实际界面中传达了它的适当性,并防止了这种不必要的耦合. (2认同)
  • "我认为这里的混淆是由于constexpr没有确保结果实际上是编译时const的任何参数" - 这部分是错误的,必须始终存在至少*one*路径,它产生一个常量表达式. (2认同)
  • @Xeo:§7.1.5/ 5"对于constexpr函数,如果不存在函数参数值,使得函数调用替换将产生常量表达式(5.19),则程序格式错误;无需诊断." (按照Potatoswatter的回答 - 在这个问题中跟随链接) - 所以"constexpr不确保......"是正确的 - 它仍然是程序员的责任. (2认同)

Pot*_*ter 16

我按下 Clang作者Richard Smith时,他解释道:

constexpr关键字确实有效用.

它会影响函数模板特化实例化的时间(如果在未评估的上下文中调用constexpr函数模板特化可能需要实例化;对于非constexpr函数也是如此;因为对一个函数的调用永远不能成为常量的一部分表达).如果我们删除了关键字的含义,我们必须尽早实例化更多的特化,以防调用恰好是一个常量表达式.

它通过限制在翻译期间尝试评估所需的实现的函数调用集来减少编译时间.(这对于需要实现以进行常量表达式求值的上下文很重要,但如果这样的评估失败则不是错误 - 特别是静态存储持续时间对象的初始化器.)

这一切看起来并不令人信服,但如果你仔细研究细节,事情就会解决constexpr.在使用ODR之前,不需要实例化函数,这实际上意味着在运行时使用.功能的特殊之处constexpr在于它们可以违反此规则并且无论如何都需要实例化.

函数实例化是一个递归过程.实例化一个函数会导致它使用的函数和类的实例化,而不管任何特定调用的参数.

如果在实例化此依赖关系树时出现问题(可能花费很大),则很难吞下错误.此外,类模板实例化可能具有运行时副作用.

给定函数签名中依赖于参数的编译时函数调用,重载解析可能导致函数定义的实例化仅仅是对重载集中的函数定义的辅助,包括甚至不被调用的函数.这种实例化可能具有副作用,包括不良形式和运行时行为.

这是一个确定的角落案例,但如果你不要求人们选择加入constexpr函数,就会发生不好的事情.

  • 有什么例子吗?我不明白。如果该函数未在编译时表达式中使用,则不需要实例化。 (2认同)