为什么我们需要将函数标记为constexpr?
C ++ 11允许使用constexpr
说明符声明的函数在常量expression式(如模板参数)中使用。 有关什么是可以被constexpr
有严格的要求; 本质上这样一个函数只封装一个子expression式,没有别的。 (编辑:这是放松的C + + 14中,但问题的立场。)
为什么要求关键字呢? 获得了什么?
它确实有助于揭示接口的意图,但是它并没有validation意图,通过保证函数在常量expression式中是可用的。 在编写constexpr
函数之后,程序员仍然必须:
- 编写一个testing用例或以其他方式确保它在一个常量expression式中被使用。
- 在常量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