C++ 规格要求数组大小的定义上,以及枚举值(enumerator values)都要求必须是编译器常数表示式:
例如:
int getCount(){
return 5;
}
const int count1 = 5;
const int count3 = getCount();
int count2 = 5;
int a1[5];
int a2[count1];
int a3[count2]; //compile error, 这不是合法的 C++, count2非编译期常数
int a4[getCount()]; //compile error, 这不是合法的 C++, getCount()非编译期常数
int a5[count3]; //cimpile error, 这不是合法的 C++, count3非编译期常数
enum V1{
ENUM_V1_A = count1,
ENUM_V1_B,
};
enum V2{
ENUM_V2_A = count2, //compile error, 这不是合法的 C++, count2非编译期常数
ENUM_V2_B,
};
然而实际上getCount()和count2 总是产生相同的结果,但编译器无法得知。 理论上而言,这个函数可能会影响全局参数,或者调用其他的非运行期(non-runtime)常数函数等。
C++11引进关键字 constexpr 允许用户保证函数或是对象建构式是编译期常数。以上的例子可以被写成像是下面这样:
constexpr int getCount(){
return 5;
}
const int count1 = 5;
const int count3 = getCount();
constexpr int count2 = 5;
int a1[5];
int a2[count1];
int a3[count2];
int a4[getCount()];
int a5[count3];
enum V1{
ENUM_V1_A = count1,
ENUM_V1_B,
};
enum V2{
ENUM_V2_A = count2,
ENUM_V2_B,
};
这使得编译器能够了解并去验证 getCount()和count2 是个编译期常数。
常函数必须满足如下标准(647 号议题,2008.6):
- 非虚函数。
- 返回值和参数表都须为“字面类型(Literal Type)”。
- 函数体只能是 return expression; 的形式,expression 应为潜在的常数表达式。
- 函数体被声明为 constexpr。
- 由 expression 到返回类型的隐式转化(implicit conversion)须为常表达式允许的转化形式之一。
对函数使用 constexpr 在函数可以做的事上面加上了非常严格的条件。
首先,该函数的回返值类型不能为 void。
第二点,函数的内容必须依照 "return expr" 的形式。
第三点,在引数取代后,expr 必须是个常数表示式。这些常数表示式只能够调用其他被定义为 constexpr 的函数,或是其他常数表示式的数据参数。
最后一点,有着这样标签的函数直到在该编译单元内被定义之前是不能够被调用的。
extern constexpr int doSomething5();
//Compile error4, 当前位置doSomething5()没有定义
int a[doSomething5()];
//Compile error1, 返回值不能为void
constexpr void doSomething1(){
}
//Compile error2, 返回值不为常量表达式
constexpr int doSomething2(){
int a = 5;
return a + 5;
}
const int COUNT = 5;
//返回值为常量表达式
constexpr int doSomething3(){
return COUNT + 5;
}
//返回值为常量表达式
constexpr int doSomething5(){
return 5;
}
//当前位置doSomething5()有定义
int b[doSomething5()];
//返回值为常量表达式
constexpr int doSomethings4(){
return doSomething3() + 5;
}
constexpr int doSomething7(int v){
return v + 5;
}
int main(int argc, const char * argv[])
{
constexpr int v1 = doSomething7(1);
//compile error, argc不为常量表达式
constexpr int v2 = doSomething7(argc);
}
参数也可以被定义为常数表示式值:
constexpr int intValue = 5; constexpr float floatValue1 = 3.5f; constexpr float floatValue2 = 3.5f / intValue;
常数表示式的数据参数是隐式的常数。他们可以只存储常数表示式或常数表示式建构式的结果。
为了从用户自定类型(user-defined type)建构常数表示式的数据参数,构造函数也可以被声明成 constexpr。与常数表示式函数一样,常数表示式的构造函数必须在该编译单元内使用之前被定义。他必须有着空的函数本体。它必须用常数表示式初始化他的成员(member)。而这种类型的析构式应当是无意义的(trivial),什么事都不做。
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 建构起来的类型也应该被定义为 constexpr,这样可以让他们从常数表示式的函数以值传回。
类型的任何成员函数,像是复制重载的运算符等等,只要他们符合常数表示式函数的定义,都可以被声明成 constexpr。这使得编译器能够在编译期进行类型的复制、对他们施行运算等等。
常数表示式函数或建构式,可以以非常数表示式(non-constexpr)参数唤起。就如同 constexpr 整数字面值能够指派给 non-constexpr 参数,constexpr 函数也可以接受 non-constexpr 参数,其结果存储于 non-constexpr 参数。constexpr 关键字只有当表示式的成员都是 constexpr,才允许编译期常数性的可能。