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(); // 格式错误:一个立即函数指针不允许作为一个常量表达式结果
相关推荐
澈2071 小时前
二叉搜索树:高效增删查的秘诀
java·开发语言·算法
米啦啦.1 小时前
STL(标准模板库)
开发语言·c++·stl
咩咦1 小时前
C++学习笔记08:指针和引用的区别
c++·学习笔记·指针·引用·指针和引用
lly2024061 小时前
建造者模式:构建复杂对象的最佳实践
开发语言
洛水水2 小时前
【力扣100题】34.二叉搜索树中第K小的元素
c++·算法·leetcode
无尽冬.2 小时前
个人八股之string字符串
java·开发语言·经验分享·后端·异世界
许长安2 小时前
gRPC Keepalive 机制
c++·经验分享·笔记·rpc
吃好睡好便好2 小时前
在Matlab中绘制抛物三维曲面图
开发语言·人工智能·学习·算法·matlab·信息可视化
半步仙人2 小时前
MATLAB的几种取整操作总结
开发语言·matlab