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

C ++ 11允许使用constexpr说明符声明的函数在常量expression式(如模板参数)中使用。 有关什么是可以被constexpr有严格的要求; 本质上这样一个函数只封装一个子expression式,没有别的。 (编辑:这是放松的C + + 14中,但问题的立场。)

为什么要求关键字呢? 获得了什么?

它确实有助于揭示接口的意图,但是它并没有validation意图,通过保证函数在常量expression式中是可用的。 在编写constexpr函数之后,程序员仍然必须:

  1. 编写一个testing用例或以其他方式确保它在一个常量expression式中被使用。
  2. 在常量expression式上下文中logging哪些参数值是有效的。

与揭示意图相反,装饰函数与constexpr可能会增加一个错误的安全感,因为切向语法约束被检查而忽略了中心语义约束。


简而言之 ,如果函数声明中的constexpr只是可选的,会不会对语言产生不良影响? 或者是否对任何有效的程序都有影响?

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

说我正在写一个库,并在那里有一个函数,目前返回一个常量:

awesome_lib.h:

 inline int f() { return 4; } 

如果不需要constexpr ,那么作为客户端代码的作者,您可能会离开并执行如下操作:

client_app.cpp:

 #include <awesome_lib.h> int my_array[f()]; 

然后,我应该改变f()说从configuration文件返回的值,您的客户端代码将打破,但我不知道我冒着打破你的代码冒险。 事实上,这可能只有当你有一些生产问题,并重新编译,你会发现这个额外的问题,挫败你的重build。

通过改变f()实现 ,我可以有效地改变接口的用法。

相反,C ++ 11向前提供了constexpr所以我可以表示客户端代码可以对剩余的constexpr函数有一个合理的期望,并使用它。 我知道并认可这种用法作为我的界面的一部分。 就像在C ++ 03中一样,编译器继续保证客户端代码不依赖于其他的非constexpr函数来防止上面的“不需要/未知的依赖”情况; 这不仅仅是文档 – 它是编译时间的执行。

值得注意的是,这继续了为传统的预处理macros提供更好的替代scheme的C ++趋势(考虑#define F 4 ,以及客户程序员如何知道lib程序员是否认为它是公平的游戏来改变说#define F config["f"] ),以及其众所周知的”罪恶“,如在语言的命名空间/类范围界定系统之外。

为什么没有对“显然”从来没有const函数的诊断?

我认为这里的困惑是由于constexpr没有主动确保有一组参数的结果实际上是编译时const:它需要程序员对此负责(否则标准认为程序不合格,但不要求编译器发出诊断)。 是的,这是不幸的,但它并没有消除constexpr的上述效用。

所以,也许这个问题不应该是“ constexpr的要点”,而是“为什么我可以编译一个不能真正返回const值的constexpr函数?”。 答:因为需要详尽的分支分析,可能涉及任意数量的组合。 编译时间和/或内存的代价可能过于昂贵,甚至超出任何可以想象的硬件的能力来诊断。 而且,即使在实际上不得不准确诊断这些情况,编译器编写者(已经充分忙于C ++ 11实现)也是一个全新的蠕虫。 也会对程序产生影响,例如在执行validation时需要显示constexpr函数中调用的函数的定义(以及函数调用的函数等)。

与此同时, 缺乏继续禁止使用作为一个常数值:严格的是在constexpr一面。 如上所述,这很有用。

与非“const`成员函数进行比较

  • constexpr可以防止int x[f()]const防止const X x; xf(); const X x; xf(); – 他们都确保客户端代码不硬编码不需要的依赖

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

    • 你不希望客户端代码在一个const对象上调用一个成员函数,当你已经可以预见到这个函数将会发展到修改可观察值,破坏客户端代码

    • 如果您已经预计到它将在运行时确定,那么您不需要将值用作模板参数或数组维度

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

当我叮嘱Clang作者Richard Smith时,他解释说:

constexpr关键字确实有用。

它影响到一个函数模板专门化的实例化(constexpr函数模板特化可能需要被实例化,如果它们在未被评估的上下文中调用;对于非constexpr函数也是如此,因为调用一个函数永远不能成为常量的一部分expression)。 如果我们删除了关键字的含义,那么我们就必须尽早实例化更多的专门化,以防万一这个调用是一个常量expression式。

它减less了编译时间,通过限制在转换过程中需要实现的函数调用集。 (这对于需要实现常量expression式评估的上下文很重要,但是如果这样的评估失败,这并不是一个错误 – 特别是静态存储持续时间的对象的初始化。

起初这一切似乎并不令人信服,但是如果你constexpr细节,那么事情就会constexpr解开。 一个函数在被ODR使用之前不需要被实例化,这实际上意味着在运行时使用。 constexpr函数的特殊之处在于它们可以违反这个规则并且要求实例化。

函数实例化是一个recursion过程。 实例化一个函数会导致实例化它所使用的函数和类,而不pipe任何特定调用的参数。

如果在实例化这个依赖关系树的时候出了点问题(可能花费很大的代价),那么很难吞下这个错误。 此外,类模板实例化可能具有运行时副作用。

给定函数签名中依赖于参数的编译时函数,重载parsing可能导致函数定义的实例化,这些函数定义仅仅是过载集合中的函数定义的辅助,包括甚至不被调用的函数。 这样的实例可能有副作用,包括不良forms和运行时行为。

可以肯定的是,如果不要求用户select使用constexpr函数,那么可能会发生不好的事情。

没有关键字,编译器不能诊断错误。 编译器将无法告诉您该函数在语法上作为constexpr是无效的。 虽然你说这样提供了一种“虚假的安全感”,但我相信最好早点把握这些错误。

我们可以没有constexpr生活,但在某些情况下,它使代码更容易和直观。
例如,我们有一个类声明一个具有一定引用长度的数组:

 template<typename T, size_t SIZE> struct MyArray { T a[SIZE]; }; 

通常你可以声明MyArray为:

 int a1[100]; MyArray<decltype(*a1), sizeof(a1)/sizeof(decltype(a1[0]))> obj; 

现在看看它如何与constexpr

 template<typename T, size_t SIZE> constexpr size_t getSize (const T (&a)[SIZE]) { return SIZE; } int a1[100]; MyArray<decltype(*a1), getSize(a1)> obj; 

简而言之,只有编译器将其识别为constexpr才能将任何函数(例如getSize(a1) )用作模板参数。

constexpr也用来检查负逻辑。 它确保给定的对象在编译时。 这里是参考链接例如

 int i = 5; const int j = i; // ok, but `j` is not at compile time constexprt int k = i; // error