在我看来,拥有一个“总是返回5的函数”破坏或稀释了“调用函数”的意义。必须有一个原因,或者需要这个功能,否则它就不会出现在c++ 11中。为什么会在那里?
// preprocessor.
#define MEANING_OF_LIFE 42
// constants:
const int MeaningOfLife = 42;
// constexpr-function:
constexpr int MeaningOfLife () { return 42; }
在我看来,如果我写一个函数,返回一个字面值,然后我进行代码检查,有人会告诉我,我应该声明一个常量值,而不是返回5。
它可以实现一些新的优化。Const传统上是类型系统的提示,不能用于优化(例如,Const成员函数可以合法地const_cast并修改对象,因此Const不能用于优化)。
Constexpr表示表达式真的是常量,前提是函数的输入是const。考虑:
class MyInterface {
public:
int GetNumber() const = 0;
};
如果这在其他模块中公开,编译器不能相信GetNumber()在每次调用时不会返回不同的值——甚至在两次调用之间没有非const调用的情况下也不会返回不同的值——因为const可能已经在实现中被丢弃了。(显然,任何这样做的程序员都应该被枪毙,但语言允许这样做,因此编译器必须遵守这些规则。)
添加constexpr:
class MyInterface {
public:
constexpr int GetNumber() const = 0;
};
编译器现在可以应用优化,缓存GetNumber()的返回值,并消除对GetNumber()的额外调用,因为constexpr是一个更强的保证,返回值不会改变。
这里的许多回答似乎有些相反,或者把安静的部分大声说出来,把吵闹的部分小声说出来。关于constexpr你需要知道的一件关键的事情是:
// This guarantees only that the value of "MeaningOfLife" can not be changed
// from the value calculated on this line by "complex_initialization()"
// (unless you cast away the const of course, don't do that).
// Critically here, everything happens at *runtime*.
const int MeaningOfLife = complex_initialization(1234, 5678, "hello");
// This guarantees that "MeaningOfLife" is fully evaluated and "initialized"
// *at compile time*. If that is not possible due to complex_initialization()
// not being evaluatable at compile time, the compiler is required to abort
// compilation of the program.
// Critically here, to put a fine point on it, everything happens at
// *compile time*, guaranteed. There won't be a runtime call to
// complex_initialization() at all in the final program.
constexpr int MeaningOfLife = complex_initialization(1234, 5678, "hello");
注意,是左边的常量使保证有存在的理由。当然,这取决于你是否能确保右边的值在编译时被求出来,重要的是,仅仅声明一个函数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
曾经有一种元编程模式:
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,在上面的例子中)。还有一个问题,就是需要有“正式的”常量表达式作为模板参数。