常量表达式(constexpr) — 一般化的受保证的常量表达式
常量表达式机制是为了:
- 提供了更多的通用的值不发生变化的表达式
- 允许用户自定义的类型成为常量表达式
- 提供了一种保证在编译期完成初始化的方法(可以在编译时期执行某些函数调用)
考虑下面这段代码:
enum Flags { good=0, fail=1, bad=2, eof=4 };
constexpr int operator|(Flags f1, Flags f2)
{ return Flags(int(f1)|int(f2)); }
void f(Flags x)
{
switch (x) {
case bad: /* … */ break;
case eof: /* … */ break;
case bad|eof: /* … */ break;
default: /* … */ break;
}
}
在这里,常量表达式关键字constexpr表示这个重载的操作符“|”就应该像一个简单的表单一样,如果它的参数本身就是常量 ,那么这个操作符应该在编译时期就应该计算出它的结果来。(译注: 我们都知道,switch的分支条件要求常量,而使用constexpr关键字重载操作符“|”之后,虽然“bad|eof”是一个表达式,但是因为这两个参数都是常量,在编译时期,就可以计算出它的结果,因而也可以作为常量对待。)
除了可以在编译时期被动地计算表达式的值之外,我们希望能够主动地要求表达式在编译时期计算其结果值,从而用作其它用途,比如对某个变量进行赋值。当我们在变量声明前加上constexpr关键字之后,可以实现这一功能,当然,它也同时会让这个变量成为常量。
constexpr int x1 = bad|eof; // ok
void f(Flags f3)
{
// 错误:因为f3不是常量,所以无法在编译时期计算这个表达式的结果值
constexpr int x2 = bad|f3;
int x3 = bad|f3; // ok,可以在运行时计算
}
通常,我们希望编译时期计算可以保护全局或者名字空间内的对象,对名字空间内的对象,我们希望它保存在只读空间内。
对于那些构造函数比较简单,可以成为常量表达式(也就是可以使用constexpr进行修饰)的对象可以做到这一点(?)
struct Point {
int x,y;
constexpr Point(int xx, int yy) : x(xx), y(yy){}
};
constexpr Point origo(0,0);
constexpr int z = origo.x;
constexpr Point a[] = {Point(0,0), Point(1,1), Point(2,2) };
constexpr x = a[1].x; // x 变成 1 (译注:这里的代码似乎有问题?)
(zwvista的一段评论,有助于我们理解constexpr的意义,感谢zwvista。constexpr 将编译期常量概念延伸至括用户自定义常量以及常量函数,其值的不可修改性由编译器保证,因而constexpr 表达式是一般化的,受保证的常量表达式。)
参考:
- the C++ draft 3.6.2 Initialization of non-local objects, 3.9 Types [12], 5.19 Constant expressions, 7.1.5 The constexpr specifier
- [N1521=03-0104] Gabriel Dos Reis: Generalized Constant Expressions (original proposal).
- [N2235=07-0095] Gabriel Dos Reis, Bjarne Stroustrup, and Jens Maurer: Generalized Constant Expressions — Revision 5 .
(翻译:陈良乔,感谢:zwvista)