constexpr
是 C++11 引入的,一方面是为了引入更多的编译时计算能力,另一方面也是解决 C++98 的 const
的双重语义问题。
在 C 里面,const
很明确只有 「只读」 一个语义,不会混淆。C++ 在此基础上增加了 「常量」 语义,也由 const 关键字来承担,引出来一些奇怪的问题。C++11 把 「常量」 语义拆出来,交给新引入的 constexpr
关键字。
演示一下 const
关键字的双重语义可能带来的的问题:
template<int N> class C{};
constexpr int FivePlus(int x) { return 5 + x; }
void f(const int x) {
C<x> c1; // Error: x is not compile-time evaluable.
C<FivePlus(6)> c2; // OK
}
void g() {
const int x = 5;
C<x> c1; // OK!!! 此处用x的「常量」语义
*(int *)(&x) = 6; // Still OK! 只处用x的「只读」语义,去除const后便可写了; (int*)是强制类型转换,有些编译器可能会报错
// 如果上一句报错,就可以写成下面这一句:
// *const_cast<int*>(&x) = 6;
C<x> c2; // Still OK! c2是C<5>类型(不是C<6>!)
C<FivePlus(x)> c3; // Still OK! c3是C<10>类型(不是C<11>!)
printf("%d\n", x); // 此处绝大多数(所有?)C++编译器会输出5!!
// (然而,如果用一个C编译器来编译类似代码,一定输出6)
const int* p = &x;
printf("%d\n", *p); // 此处,大多数C++编译器输出6
}
可以看到,f 和 g 都有一个 const int x,但它们的行为却不同。原因在于:f 的 const int x 只是「一个只读的变量」;而 g 的 const int x 既是「一个只读的变量」,又是「一个值为5的常量」,变得飘忽不定。
在 C++11 以后,建议凡是 「常量」 语义的场景都使用 constexpr
,只对 「只读」 语义使用 const
。
可能有人就会糊涂了,只读变量 难道不就是 常量吗?然则非也。
同样一个内存地址,用常量指针关联时,通过这一路径就无法修改;换用非常量指针 关联时,在这条路径上就是可以修改的。
换句话说,用 const
限定变量时,只是剥夺了通过该变量修改相应内存中内容的可能性,但是有可能其他程序或其他指向该内存地址的变量会改变这块内存中的内容,也就是说这块内存地址空间的内容并不会保证一直不变。所以,从现在开始,就把const
理解成只读的限定符,把constexpr
理解成常量的限定符。