C++ 一直在不断增加新特性,以便程序员能够区分在编译时运行的代码和在运行时执行的代码。其中两个重要工具是函数 std::is_constant_evaluated()(C++20)和语言级别的 if consteval(C++23)。本文将解释这两者,展示实际示例,比较它们的保证和权衡,并建议在何时使用各自的方法。
这两种技术都允许你编写分支,根据当前的求值是在常量求值(编译时)上下文中还是运行时上下文中而表现不同。差异虽然细微,但非常重要:一个是返回布尔值的函数,另一个是编译器视为仅在编译时检查的特殊 if 语句,编译器会进行特殊处理。
std::is_constant_evaluated() (C++20)
这是一个在 <type_traits> 中声明的函数:
#include <type_traits>
constexpr bool std::is_constant_evaluated() noexcept;
当当前表达式在常量表达式(编译时)上下文中求值时,该函数返回 true,否则返回 false。
示例:
#include <iostream>
#include <type_traits>
constexpr int f() {
if (std::is_constant_evaluated()) {
return 42; // 编译时
} else {
return 0; // 运行时
}
}
int main() {
constexpr int a = f(); // 编译时求值 -> 42
int b = f(); // 运行时求值 -> 0
std::cout << a << ", " << b << "\n"; // 打印 "42, 0"
}
if consteval (C++23)
if consteval 是一个语言级别的 if 条件语句,编译器用它来判断当前求值上下文是否为常量求值。它提供了更强的编译时保证:编译器知道 consteval 分支在常量求值中必须可用,并且会拒绝不能在该上下文中出现的代码。
语法:
if consteval {
// 仅编译时代码
} else {
// 仅运行时代码
}
示例:
#include <iostream>
constexpr int f() {
if consteval {
return 42; // 编译时版本
} else {
return 0; // 运行时版本
}
}
int main() {
constexpr int a = f(); // 编译时上下文 -> 返回 42
int b = f(); // 运行时上下文 -> 返回 0
std::cout << a << ", " << b << "\n"; // 打印 "42, 0"
}
主要区别(总结)
- 形式: std::is_constant_evaluated() 是一个函数;if consteval 是一个语言级条件语句。
- 标准: std::is_constant_evaluated() 出现在 C++20 中;if consteval 出现在 C++23 中。
- 保证: if consteval 为编译器提供编译时保证,并拒绝编译时无法使用的 consteval 分支中的代码。 std::is_constant_evaluated() 的执行环境更为宽松:它返回布尔值,但不会强制编译器拒绝其他分支中无效的编译时构造。
- 在表达式内部使用: std::is_constant_evaluated() 可以在语句形式的 if 无法使用的表达式内部使用(例如,在三元运算符内部)。if consteval 需要语句级上下文。
具体比较示例
两个函数看起来相似,但在执行方面表现不同:
// if-consteval 示例
constexpr int f() {
if consteval {
return 1; // 仅在编译时
} else {
return 2; // 仅限运行时
}
}
// std::is_constant_evaluated() 示例
#include <type_traits>
constexpr int g() {
if (std::is_constant_evaluated()) {
return 1; // 可能仍会编译运行时路径
} else {
return 2;
}
}
以下场景中,差异至关重要:
// 使用 if consteval 时,编译器将拒绝无效的仅限编译时构造
constexpr int bad() {
if consteval {
std::cout << "compile-time"; // ❌ 错误 — 常量求值中不允许 I/O
}
return 0;
}
// 使用 std::is_constant_evaluated() 时,编译器会更加宽容,可能会编译通过,直到代码
// 实际用于常量表达式上下文。
#include <type_traits>
constexpr int perhaps_bad() {
if (std::is_constant_evaluated()) {
std::cout << "compile-time"; // 可能会编译通过;只有在 const-expr 中求值时才会出现错误
}
return 0;
}
使用场景
- 如果您的目标是 C++23(或更高版本),并且想要清晰的、编译时强制的分支:请优先使用
if consteval。 - 如果您必须仅支持 C++20 环境:请使用
std::is_constant_evaluated()。 - 如果您需要在表达式(而非语句)中进行检查,请使用
std::is_constant_evaluated(),因为if consteval是语句级的。 - 如果您希望编译器拒绝编译时无法使用的代码,
if consteval可以提供更强的保证。
实际用例
- 为编译时和运行时(快速预计算 vs 较慢的运行时)编写不同的实现逻辑)。
- 保护仅运行时操作(例如 I/O、动态分配或系统调用),以免它们被意外地用于常量求值路径。
- 在编写可在 constexpr 上下文和正常运行时上下文中使用的库时,提供更清晰、更能揭示意图的代码。
简短实用的检查清单
- 需要表达式级检查?→
std::is_constant_evaluated()。 - 想要编译器强制执行的仅编译时分支?→
if consteval。 - 仅针对 C++20?→
std::is_constant_evaluated()。 - 针对 C++23+ 并更注重清晰度和安全性? →
if consteval.
TL;DR
std::is_constant_evaluated() — C++20:在编译时求值中返回 true 的函数(适用于表达式级检查)。
if consteval — C++23:
- 编译器将其视为仅编译时分支的语言级条件语句;
- 更强的编译时执行力和更清晰的意图。
结束语
这两个工具都很有用。如果您可以使用 C++23,则最好使用 if consteval 以获得更清晰的语义和更强的编译时保证,并在与 C++20 兼容或需要表达式级检查时回退到 std::is_constant_evaluated()
C/C++编程
- 理解C++中的std::transform_reduce及示例
- 使用原子 TAS 指令实现自旋锁
- C++中检测编译时与运行时: if consteval 与 std::is_constant_evaluated()
- C++ 转发引用: 完美转发的关键
- 理解 C++ 中的 dynamic_cast: 安全的向下转型与向上转型
- C与C++: restrict关键字及其在编译器优化中的作用
- C++的左值/lvalue, 右值/rvalue和右值引用/rvalue references
- C++中的assert和static_assert的区别
- C++: auto_ptr智能指针被弃用
- C++中的consteval是什么? 它与const和constexpr有何不同?
- C++ 教程: 用std::move来移动所有权
- C++中的 const和constexpr 比较
- 简易教程: C++的智能指针
- C++ 编程练习题: 如何合并两个二叉树?
- C++ 编程练习题 - 找出第三大的数
- C++ 编程练习题 - 最多连续的 1
- C++ 编程练习题 - 左子树叶节点之和 (深度优先+广度优先+递归)
- C++ 编程练习题 - 最多水容器 (递归)
- C++的异步编程: std::future, std::async 和 std::promise
- C编程练习题: 翻转整数位
- C++编程练习题: 找出字符串的所有大小小组合
- C/C++ 中的内存管理器(堆与栈)
- C++编程练习题: 对两单向链表求和
英文:Detecting Compile-time vs Runtime in C++: if consteval vs std::is_constant_evaluated()
强烈推荐
- 英国代购-畅购英伦
- TopCashBack 返现 (英国购物必备, 积少成多, 我2年来一共得了3000多英镑)
- Quidco 返现 (也是很不错的英国返现网站, 返现率高)
- 注册就送10美元, 免费使用2个月的 DigitalOcean 云主机(性价比超高, 每月只需5美元)
- 注册就送10美元, 免费使用4个月的 Vultr 云主机(性价比超高, 每月只需2.5美元)
- 注册就送10美元, 免费使用2个月的 阿里 云主机(性价比超高, 每月只需4.5美元)
- 注册就送20美元, 免费使用4个月的 Linode 云主机(性价比超高, 每月只需5美元) (折扣码: PodCastInit2022)
- PlusNet 英国光纤(超快, 超划算! 用户名 doctorlai)
- 刷了美国运通信用卡一年得到的积分 换了 485英镑
- 注册就送50英镑 – 英国最便宜最划算的电气提供商
- 能把比特币莱特币变现的银行卡! 不需要手续费就可以把虚拟货币法币兑换
微信公众号: 小赖子的英国生活和资讯 JustYYUK