常量表达式函数
要求:
- 函数体内只有单一的return返回语句
例如:
constexpr int data()
{
const int i=1; //含有除了return以外的语句
return i;
}
在c++11中是无法通过编译的。
但使用不会产生实际代码的语句是可以的,例如static_assert()
2. 函数必须返回值
例如constexpr void f(){}
无法通过编译的,因为无法获得常量的常量表达式是不被认可的。
3. 在使用前必须已有定义
constexpr int f();
constexpr int c=f(); //无法通过编译
constexpr int f(){return 1;}
- return返回语句表达式中不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式。
const int e(){return 1;}
constexpr int g(){return e();} //编译错误,使用了非常量表达式的函数
常量表达式值
const int i=1;
constexpr int j=1;
两者在大多数情况下没有区别,但如果i在全局名称空间中,编译器一定会为i产生数据。而对于j,如果不是有代码显式地使用了它的地址,编译器可以选择不为它生成数据,而仅将其当作编译时期的值。
在C++11中constexpr是不能用于自定义类型的,例如:
constexpr struct MyType{int i;}
constexpr MyType mt={0};
将无法通过编译。正确的做法是,定义自定义常量构造函数:
struct MyType
{
constexpr MyType(int x):i(x){}
int i;
};
constexpr MyType mt = { 0 };
常量表达式的构造函数也有使用上的约束:
- 函数体必须为空
- 初始化列表只能由常量表达式来赋值。
此外,在C++11中,不允许常量表达式作用于virtual的成员函数。vitual是运行时的行为,与编译期计算的constexpr的意义是冲突的。
常量表达式的构造函数也可以用于非常量表达式中的类型构造,且无法重写。
其他应用
模板函数
常量表达式是可以用于模板函数的,不过由于模板中类型的不确定性,所以模板函数是否会被实例化为一个能够满足编译时常量性的版本通常也是未知的。C++11标准规定:当声明为常量表达式的模板函数后,而某个该模板函数的实例化结果不满足常量表达式的需求的话,constexpr会被自动忽略。该实例化后的函数将成为一个普通函数。
例如:
struct NotLiteral
{
NotLiteral(){i=5;}
int i;
};
NotListeral nl;
template<class T>
constexpr T ConstExp(T t)
{
return t;
}
void g()
{
NotLiteral nl;
NotLiteral nl1=ConstExp(nl);
constexpr NotLiteral nl2=ConstExp(nl); //无法编译
constexpr int a=ConstExp(1); //OK
}
代码中NotLiteral不是一个定义了常量表达式构造函数的类型,因此不能够声明为常量表达式值。而模板函数ConstExp一旦以NotLiteral为参数的话,那么其constexpr关键字将被忽略。
递归
常量表达式支持至少512层的递归,可以在编译期充当“计算器”。
constexpr int Fibonacci(int n)
{
return (n == 1) ? 1 : ((n == 2) ? 2 : Fibonacci(n - 1) + Fibonacci(n - 2));
}
Fibonacci(12);
然而并不是使用了constexpr,编译器就一定会在编译期间对常量表达式函数进行值计算。标准只是定义了可以用于编译时进行值计算的常量表达式的定义,却没有强制要求编译器一定在编译时进行值计算。(经反汇编,上述代码在vs2017debug下未在编译期计算出结果)所以编译器通过一些手段绕过代码的语法,仍然做运行时的调用,这样也是符合C++11的。
内容摘自《深入理解C++11》