C++ 关键字 constexpr 和 consteval 之注意事项

C++ 支持两种不可变性概念(即状态不可变的对象):

const :其大致含义是"我承诺不会更改此值"。它主要用于定义接口,以便在通过指针和引用将数据传递给函数时,无需担心数据会被修改。编译器会强制执行由 const 所作的这一承诺。const 变量的值可以在运行时计算得出。

constexpr :其大致含义是"在编译时进行求值"。它主要用于定义常量,以便将数据存放在只读内存中(从而降低数据被破坏的风险),同时也旨在提升性能。constexpr 变量的值必须由编译器在编译时计算得出。

例如:

cpp 复制代码
	constexpr int dmv = 17; // dmv 是一个命名常量
	int var = 17; // var 不是一个常量
	const double sqv = sqrt(var); // sqv 是一个命名常量,可能在运行时计算
	double sum(const vector<double>&); // sum 不会修改其参数值
	vector<double> v{ 1.2, 3.4, 4.5 }; // v 不是一个常量
	const double s1 = sum(v); // 可行: sum(v) 在运行时计算
	constexpr double s2 = sum(v); // 错误: sum(v) 不是一个常量表达式

若要使函数可在常量表达式中使用(即在由编译器进行求值的表达式中),该函数必须定义为 constexprconsteval

。例如:

cpp 复制代码
constexpr double square(double x) { return x*x; }
constexpr double max1 = 1.4*square(17); // 可行: 1.4*square(17) 是一个常量表达式,编译时计算
constexpr double max2 = 1.4*square(var); // 错误: var 不是一个常量, 因此square(var) 不是一个常量,无法在编译时进行计算
const double max3 = 1.4*square(var); // 可行: 可以在运行时计算

constexpr 函数可以接受非常量实参,但在这种情况下,其求值结果将不再是常量表达式。我们允许在不强制要求常量表达式的语境中,调用带有非常量表达式实参的 constexpr 函数。这样一来,我们便无需重复定义本质上完全相同的函数------即无需分别为常量表达式和变量各定义一个版本。若我们希望某个函数仅用于编译期求值,则应将其声明为 consteval ,而非 constexpr。例如:

cpp 复制代码
consteval double square2(double x) { return x*x; }
constexpr double max1 = 1.4*square2(17); // 可行: 1.4*square(17) 是一个常量表达式
const double max3 = 1.4*square2(var); // 错误: var 的定义不是一个常量而是变量

声明为 constexprconsteval 的函数,是 C++ 对"纯函数(pure functions)"这一概念的实现。它们不得产生副作用,且只能通过参数传递给它们的信息。具体而言,它们不能修改非局部变量,但允许包含循环结构并使用自身的局部变量。

例如:

cpp 复制代码
constexpr double nth(double x, int n) // 假设 0<=n
{
double res = 1;
int i = 0;
while (i<n) { // while循环: 条件为真则执行
res *= x;
++i;
}
return res;
}

在某些语境下,语言规则强制要求使用常量表达式(例如:数组边界 (§1.7)、case 标签 (§1.8)、模板值实参 (§7.2),以及使用 constexpr 声明的常量)。而在其他情况下,编译期求值对于提升性能至关重要。即便暂不考虑性能因素,不可变性(即对象处于一种不可更改的状态)这一概念本身也是一个重要的设计考量。

注意: consteval 是 C++20 新增的一个关键字,其旨在强制函数编译时求值(即将函数声明为一个"即时"函数),且只能用于函数或函数模板,其调用参数必须是常量,不能是变量,是对 constexpr 的进一步限制。例如:

cpp 复制代码
consteval int sqr(int n)
{
	return n * n;  //调用时参数必须是常量
}
constexpr int r = sqr(100); // 可行:编译时计算,参数是常量

int x = 100;
int r2 = sqr(x);            // 错误: 调用不会产生一个常量,参数是变量

const int x = 100;        //可行:参数是常量
int r2 = sqr(x);
constexpr int r3 = sqr(x);
const int r4 = sqr(x);    

consteval int sqrsqr(int n)
{
	return sqr(sqr(n));     // 这时不是一个常量表达式, 但可行,因为调用者是 consteval 函数
}

constexpr int dblsqr(int n)
{
	return 2 * sqr(n);      // 错误: 其外围函数不是一个 consteval
	// 且 sqr(n) 不是一常量,无法在编译时进行计算
}

consteval int f() { return 42; } //可行
consteval auto g() { return &f; }
consteval int h(int (*p)() = g()) { return p(); }
constexpr int r = h();  // 可行
constexpr auto e = g(); // 格式错误:一个立即函数指针不允许作为一个常量表达式结果
相关推荐
z落落15 小时前
C# 泛型方法(原理、类型推断、多泛型参数)+泛型效率(普通类型 VS Object装箱 VS 泛型)
开发语言·c#
L_090715 小时前
【C++】异常
开发语言·c++
liulilittle15 小时前
关于拥塞控制的几点思考
网络·c++·tcp/ip·计算机网络·信息与通信·tcp·通信
世辰辰辰16 小时前
批量修改图片/文本名子
开发语言·python·批量修改文件名
QT-Neal18 小时前
C++ 编码规范
c++
啦啦啦啦啦zzzz18 小时前
数据结构:红黑树理论
数据结构·c++·红黑树
Yolo_TvT18 小时前
C++:默认构造函数
c++
z落落18 小时前
C# 四种特殊类:抽象类、密封类、静态类、部分类
开发语言·c#
VidDown19 小时前
Webhook 调试器:让第三方回调“原形毕露”
java·开发语言·javascript·编辑器·postman
装不满的克莱因瓶19 小时前
基于 OpenResty 扩展开发实现动态服务注册与发现能力
java·开发语言·架构·openresty