1 常量表达式
在C++11中引入了一个新的关键字“constexpr”,用来修饰一个函数或者变量使其变成一个常量表达式或值。比如下面的代码将函数GetConstValue变成一个常量表达式,并用这个常量表达式声明一个数组:
constexpr int GetConstValue() {return 10;};
int a[GetConstValue()];
有了constexpr关键字,编译器就可以在编译时期计算函数GetConstValue()的值,使其可以被看作是一个编译期常量。此时,函数GetConstValue()被称为"常量表达式函数"。
并不是任何一个函数加上constexpr关键字后都可以成为“常量表达式函数”。常量表达式函数必须满足以下几点要求:
1. 函数体只有单一的return语句。(一些不会产生实际代码的语句在函数中可以使用,如static_assert, using, typedef等)注:这是C++11中的要求,在C++14中这条要求被取消了,在C++14中可以像下面这样使用constexpr function
constexpr int pow(int base, int exp) // C++14 only
{
if (exp == 0) return 1;
auto result = base;
for (int i = 1; i < exp; ++i) result *= base;
return result;
}
2. 函数必须有返回值(即不能式void函数)
3. 在被使用之前必须已有定义。
4. return返回语句表达式中不能使用非常量表达式的函数,全局数据,且必须是一个常量表达式。
2 常量表达式值
常量表达式值是一个被constexpr修饰的变量,并且这个变量必须被一个常量表达式初始化。比如:
constexpr int a = 1; //常量表达式值
cosnt int b = 1; //常量
用cosntexpr修饰的变量a和用const修饰的变量b,它们有什么区别呢?在大多数情况下,它们没有区别,不过有一点要注意,如果变量b在全局名字空间中,编译器就会为它而产生数据,对于a,编译器则可以选择不为它产生数据。
2.1 const 和constexpr的区别
const并未区分出编译期常量和运行期常量
constexpr限定在了编译期常量
然后我想对修饰函数多说两句,那就是constexpr修饰的函数,返回值不一定是编译期常量。#It is not a bug, it is a feature.#
#include <iostream>
#include <array>
using namespace std;
constexpr int foo(int i)
{
return i + 5;
}
int main()
{
int i = 10;
std::array<int, foo(5)> arr; // OK
foo(i); // Call is Ok
// But...
std::array<int, foo(i)> arr1; // Error
}
所以,对于constexpr需要两方面看待。
constexpr修饰的函数,简单的来说,如果其传入的参数可以在编译时期计算出来,那么这个函数就会产生编译时期的值,也就是说这个函数的返回值可以被当做一个编译期常量来使用,比如可以用来定义数组大小。但是,传入的参数如果不能在编译时期计算出来,那么constexpr修饰的函数就和普通函数一样了。不过,我们不必因此而写两个版本,所以如果函数体适用于constexpr函数的条件,可以尽量加上constexpr。
而检测constexpr函数是否产生编译时期值的方法很简单,就是利用std::array需要编译期常值才能编译通过的小技巧。这样的话,即可检测你所写的函数是否真的产生编译期常值了。
对于自定义类型的数据,constexpr不能用来修饰自定义类型的定义的,形如下面的代码是无法通过编译的:
constexpr class Base{int m_i;} //constexpr不能修饰Base的定义,编译错误
正确的做法是自定义常量构造函数:
class Base
{
constexpr Base(int x):m_i(x){}
int m_i;
};
constexpr Base ba = {0};
需要注意的是,常量构造函数的定义也有使用上的约束:
1. 函数体必须为空
2. 初始化列表只能由常量表达式赋值。
在C++11标准中,不允许常量表达式作用于virtual的成员函数,因为virtual表示的是运行时的行为,这与constexpr“可以在编译时期进行计算”的意义是冲突的。“在编译时期进行计算”是对编译器的一个建议,C++11标准并没有强制要求编译器一定要在编译时期对常量表达式函数进行计算。
另外,constexpr也可用于模板函数,C++11标准规定,对于一个声明为常量表达式的模板函数,如果它的某一个实例化结果不满足常量表达式的需求的话,constexpr会被自动忽略而变成一个普通函数。比如下面的代码:
class Base {int i;};
template<typename T> constexpr T GetValue(T t)
{
return t;
}
void main()
{
Base base;
Base base2 = GetValue(base);//base不是一个常量表达式值,constexpr被忽略
constexpr Base base3 = GetValue(base2);//无法编译通过
}
c++14优化
c++14放松了限制。声明为constexpr的函数可以含有以下内容:
任何声明,除了:
static或thread_local变量。
没有初始化的变量声明。
条件分支语句if和switch。
所有的循环语句,包括基于范围的for循环。
表达式可以改变一个对象的值,只需该对象的生命期在声明为constexpr的函数内部开始。包括对有constexpr声明的任何非const非静态成员函数的调用。
goto仍然不允许在constexpr函数中出现。
constexpr支持编译期的递归。例如,可以写一个constexpr函数计算斐波那契数列。
此外,C++11指出,所有被声明为constexpr的非静态成员函数也隐含声明为const(即函数不能修改*this的值)。C++14已经删除此点,非静态成员函数可以为非const
C++ 20优化
扩展适用范围,新增对虚函数的支持
解除 “禁止constexpr函数内使用try-catch语句块”的约束