constexpr是在 C++11 被引进的关键字,它的字面意思是 constant expression,常量表达式。它可以作用在变量和函数上。一个 constexpr 变量是一个编译时完全确定的常数。一个 constexpr 函数至少对于某一组实参可以在编译期间产生一个编译期常数。在C++11之后的标准中赋予了constexpr更多的特性,比如C++14 允许在 constexpr 函数中使用局部变量,从而支持了循环;在 C++17 的时候多了 if constexpr 这样的语法,使得模板编程的可读性更好......。本文仅讨论C++11中的constexpr
我们可以通过将变量声明为 constexpr 来要求它在编译期被初始化:
void f(int x)
{
int y1 = x;
constexpr int y2 = x; // Error
constexpr int y3 = 77; // OK
int m = 1;
const int n = 2;
constexpr int y4 = m; // Error
constexpr int y5 = n; // OK
}
用constexpr修饰的函数,返回值不一定是编译期常量,这是特性,不是bug。
#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函数是否产生编译时期值的方法很简单,就是利用std::array需要编译期常值才能编译通过的小技巧。这样的话,即可检测你所写的函数是否真的产生编译期常值了。
用constexpr修饰的函数还可以当做模板参数。
template<int N> class C{};
constexpr int FivePlus(int x) {
return 5 + x;
}
void f(const int x) {
C<x> c1; // Error
C<FivePlus(6)> c2; // OK
}
比如你想特化一个模板,在传入的模板参数是std::numeric_limits<int>::max()时报错。那么在没有constexpr之前,就没救了,只能用INT_MAX宏......。当然现在Max已被constexpr修饰,是可以正常当模板参数传入的,原理和FivePlus函数一样。
当然引进constexpr最主要的原因是效率,因为编译器知道这是一个常量,所以可以优化代码,比如整个function全部优化掉,从而提高运行速度。
#include <iostream>
#include <chrono>
using namespace std::chrono;
constexpr int Calculate1 (int x, int y)
{
return x * x + y * y;
}
int Calculate2 (int x, int y)
{
return x * x + y * y;
}
int main()
{
auto start1 = high_resolution_clock::now();
for(int i = 0;i < 1000000; i++)
{
// 是常量表达式
Calculate1 (11, 12);
}
auto end1 = high_resolution_clock::now();
std::cout <<"cost time: " << duration_cast<microseconds>(end1 - start1).count()<<" us" << std::endl;
auto start2 = high_resolution_clock::now();
for(int i = 0;i < 1000000; i++)
{
// 不是常量表达式, 返回值不能用constexpr修饰
Calculate2 (11, 12);
}
auto end2 = high_resolution_clock::now();
std::cout <<"cost time: " << duration_cast<microseconds>(end2 - start2).count() << " us" << std::endl;
return 0;
}
可以看到循环100万次,函数Caculate1耗时更少,如果用变量接收函数返回值,对比更明显。
constexpr int ret = Calculate1 (11, 12);
int ret = Calculate2 (11, 12);