在我看来,拥有一个“总是返回5的函数”破坏或稀释了“调用函数”的意义。必须有一个原因,或者需要这个功能,否则它就不会出现在c++ 11中。为什么会在那里?
// preprocessor.
#define MEANING_OF_LIFE 42
// constants:
const int MeaningOfLife = 42;
// constexpr-function:
constexpr int MeaningOfLife () { return 42; }
在我看来,如果我写一个函数,返回一个字面值,然后我进行代码检查,有人会告诉我,我应该声明一个常量值,而不是返回5。
曾经有一种元编程模式:
template<unsigned T>
struct Fact {
enum Enum {
VALUE = Fact<T-1>*T;
};
};
template<>
struct Fact<1u> {
enum Enum {
VALUE = 1;
};
};
// Fact<10>::VALUE is known be a compile-time constant
我相信引入constexpr是为了让你编写这样的构造,而不需要模板和带有特化的奇怪构造,SFINAE之类的东西——但就像你编写一个运行时函数一样,但保证结果将在编译时确定。
但是,请注意:
int fact(unsigned n) {
if (n==1) return 1;
return fact(n-1)*n;
}
int main() {
return fact(10);
}
用g++ -O3编译它,你会看到事实(10)确实在编译时被求值了!
一个VLA-aware编译器(C99模式下的C编译器或带有C99扩展的c++编译器)甚至可以允许你做:
int main() {
int tab[fact(10)];
int tab2[std::max(20,30)];
}
但目前它是非标准的c++ - constexpr看起来是一种解决这个问题的方法(即使没有VLA,在上面的例子中)。还有一个问题,就是需要有“正式的”常量表达式作为模板参数。
简介
引入Constexpr并不是为了告诉实现可以在需要常量表达式的上下文中求值;一致性实现在c++ 11之前就已经证明了这一点。
实现无法证明的是某段代码的意图:
开发人员想用这个实体表达什么?
我们是否应该盲目地允许在常量表达式中使用代码,仅仅因为它碰巧有效?
没有警察,这个世界将会怎样?
假设您正在开发一个库,并意识到您希望能够计算区间(0,N]中每个整数的和。
int f (int n) {
return n > 0 ? n + f (n-1) : n;
}
缺乏意图
如果在转换过程中传递的参数是已知的,编译器可以很容易地证明上述函数在常量表达式中是可调用的;但你并没有宣布这是一个意图——这只是碰巧的情况。
现在另一个人来了,读取你的函数,做和编译器一样的分析;“哦,这个函数在常量表达式中可用!”,然后写了下面的代码。
T arr[f(10)]; // freakin' magic
优化
作为一个“了不起的”库开发人员,您决定f应该在被调用时缓存结果;谁会想要一遍又一遍地计算同一组值呢?
int func (int n) {
static std::map<int, int> _cached;
if (_cached.find (n) == _cached.end ())
_cached[n] = n > 0 ? n + func (n-1) : n;
return _cached[n];
}
结果
通过引入愚蠢的优化,您破坏了在需要常量表达式的上下文中对函数的所有使用。
您从未承诺过函数在常量表达式中是可用的,没有constexpr,就无法提供这样的承诺。
为什么我们需要constexpr?
constexpr的主要用途是声明意图。
如果一个实体没有被标记为constexpr——它从未打算在常量表达式中使用;即使是这样,我们也依赖编译器来诊断这样的上下文(因为它忽略了我们的意图)。
假设它做了一些更复杂的事情。
constexpr int MeaningOfLife ( int a, int b ) { return a * b; }
const int meaningOfLife = MeaningOfLife( 6, 7 );
现在您有了一些可以计算到一个常数的东西,同时保持良好的可读性,并允许稍微复杂一些的处理,而不仅仅是将一个常数设置为一个数字。
它基本上为可维护性提供了很好的帮助,因为它使您正在做的事情变得更加明显。以max(a, b)为例
template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }
这是一个非常简单的选择,但这意味着如果你用常量值调用max,它是在编译时显式计算的,而不是在运行时。
另一个很好的例子是DegreesToRadians函数。每个人都觉得角度比弧度更容易读。虽然你可能知道180度是3.14159265 (Pi)弧度,但下面写得更清楚:
const float oneeighty = DegreesToRadians( 180.0f );
这里有很多好的信息:
http://en.cppreference.com/w/cpp/language/constexpr
Stroustrup在“Going Native 2012”大会上的演讲如下:
template<int M, int K, int S> struct Unit { // a unit in the MKS system
enum { m=M, kg=K, s=S };
};
template<typename Unit> // a magnitude with a unit
struct Value {
double val; // the magnitude
explicit Value(double d) : val(d) {} // construct a Value from a double
};
using Speed = Value<Unit<1,0,-1>>; // meters/second type
using Acceleration = Value<Unit<1,0,-2>>; // meters/second/second type
using Second = Unit<0,0,1>; // unit: sec
using Second2 = Unit<0,0,2>; // unit: second*second
constexpr Value<Second> operator"" s(long double d)
// a f-p literal suffixed by ‘s’
{
return Value<Second> (d);
}
constexpr Value<Second2> operator"" s2(long double d)
// a f-p literal suffixed by ‘s2’
{
return Value<Second2> (d);
}
Speed sp1 = 100m/9.8s; // very fast for a human
Speed sp2 = 100m/9.8s2; // error (m/s2 is acceleration)
Speed sp3 = 100/9.8s; // error (speed is m/s and 100 has no unit)
Acceleration acc = sp1/0.5s; // too fast for a human