跳转到内容

为什么引入`constexpr`和`consteval`?

确保表达式可以在编译时求值。将昂贵的计算从运行时转移到编译时以显著提高性能。

例子1 使用constconstexpr

template <int SomeInt>
struct Resource {
int value {SomeInt};
};
int main() {
int value{3};
Resource<value> R;
}

上面的例子将编译出错,提示:Non-type template argument is not a constant expression

int value{3};在运行时求值,而Resource<>的非类型模板参数SomeInt需要在编译时求值,在编译时value的值还不知道,所以出错。

解决的办法就是让value在编译时求值。

可使用constconstexpr关键字。

const int value{3};
// constexpr int value{3};
Resource<value> R;

const为什么也可用?const表达式在运行时求值,但const 变量也可以作为编译时常量,只要它是一个常量表达式。不过,const的业务太繁忙了,如果要确保在编译时求值,还是用constexpr吧。

例子2 constexpr函数

看一个只能用constexpr、不能用const的例子。

constexpr int get_int() {
return 42;
}
int main() {
constexpr int value {get_int()};
}

函数get_int()被标记为constexpr,编译器确保在编译时对函数求值,所以函数返回值可用于初始化一个 constexpr 变量。

当然,constexpr函数的返回值也可作为模板的非类型模板参数。

template <int SomeInt>
struct Resource {
int value {SomeInt};
};
constexpr int get_int() {
return 42;
}
int main() {
Resource<get_int()> R;
}

例子3 consteval函数

函数被标记为constexpr,并不意味着该函数只能在编译时使用,运行时也可使用。如下:

#include <iostream>
int get_number() {
int x;
std::cout << "Enter a integer: ";
std::cin >> x;
return x;
}
constexpr int add(int x, int y) {
return x + y;
}
int main() {
std::cout << add(get_number(), get_number()) << std::endl;
}

函数add()是一个constexpr函数,但是在上面的例子中是在运行时求值。

如果想确保一个函数只能在编译时使用、不能在运行时使用,要是在运行时使用了就无法编译通过,怎么做到?

在函数声明中使用consteval关键字。

如把上面add()函数标记为consteval,则上面的程序会编译出错:error: call to consteval function ‘add(get_number(), get_number())’ is not a constant expression

例子4 运行时使用consteval函数?

既然consteval函数只能在编译时使用,无法在运行时使用,那为什么下面这个程序能编译成功呢?

#include <iostream>
consteval int add(int x, int y) {
return x + y;
}
int main() {
std::cout << add(1, 2) << std::endl;
}

在语句std::cout << add(1, 2) << std::endl;中,add(1, 2)的确是在编译时计算,结果为3,在代码中add(1, 2)3代替。在运行时,main()中的语句看起来是std::cout << 3 << std::endl;,就好像add(1, 2)压根不存在。

那为什么std::cout << add(get_number(), get_number()) << std::endl;会编译出错?因为get_number()的值是在运行时计算,在编译时add()无法求值,所以会出错。

参考