普通视图

C++中检测编译时与运行时: if consteval 与 std::is_constant_evaluated()

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() [show_file file="/var/www/wp-post-common/justyy.com/cpp.php"] 英文:Detecting Compile-time vs Runtime in C++: if consteval vs std::is_constant_evaluated()

相关文章:

  1. 简易教程: C++的智能指针 C++ 智能指针教程 C++ 中的智能指针提供了自动且安全的内存管理。它们通过 RAII(资源获取即初始化)机制,帮助开发者避免内存泄漏和悬空指针的问题,确保对象在生命周期结束时被正确释放。 本教程将介绍 C++ 中三种主要的智能指针: std::unique_ptr:独占式所有权 std::shared_ptr:共享式所有权 std::weak_ptr:非拥有式弱引用 1. std::unique_ptr unique_ptr 拥有独占所有权。一个资源只能被一个...
  2. C++中的 const和constexpr 比较 C++ const 与 constexpr:真正的区别是什么? 一眼看都是定义常量。 为什么这很重要 现代 C++ 鼓励编写不可变、高效且表达力强的代码。两个关键字—const 和 constexpr—是这一理念的核心。它们看起来很相似,但理解它们的不同语义,对于正确利用编译期与运行期行为至关重要。 高层次对比 特性 const constexpr...
  3. 借助AI快速开源了N个小工具: 写代码越来越像做产品了, AI 真把我宠坏了(Vibe Coding) 程序员的未来?Vibe Coding + AI 一起上! 借助 AI 快速开源了三个小工具 最近,我利用 ChatGPT-4o 和 o4-mini 快速开发并开源了几个小工具。起因其实很简单——每次想转换 YAML/JSON 或进行...
  4. 被动收入之: 微博红包 今年开始重新经营我的微博帐号 drlai 收到两笔微信红包,应该是来自于官方的支持,150元(成功提现到支付宝)。虽然这不能持久,也没多少,但毕竟实现了零的突破,意义重大。 如果流量上来,内容创作者可能会接受到比较多的赞赏,这也是一个比较简单的变现方法。这也能作为一种被动收入,不过如果不是头部网红,可能杯水车薪,但如果你有好几个类似这样的,也能积少成多! 在用户中心,微博用户可以每天登陆手机微博APP打卡,获取点数和少量的红包钱(几分钱),积少成多! 微博做些小任务可获得积分和几分钱。聊胜于无。 微博的主要盈利模式 微博的主要盈利模式主要包括以下几个方面: 广告收入:微博的大部分收入来源于广告,尤其是品牌广告和效果广告。广告形式包括信息流广告(类似于推文广告)、热门话题广告、开屏广告和视频广告。品牌和企业可以利用微博庞大的用户群和社交互动来提升曝光率、推广品牌和产品。 会员服务:微博提供的VIP会员服务,用户可以支付订阅费用来享受更多的特权,比如个性化的主题、特有的表情包、私密权限设置等。这些会员服务主要面向个人用户,提升其社交体验。 直播和打赏:微博提供直播平台,用户可以通过购买虚拟礼物来支持主播,微博会从这些打赏中抽取一定比例的分成。此外,微博与内容创作者分成,通过内容付费、知识付费等形式变现。 增值服务:针对企业和大V(拥有大量粉丝的用户),微博还提供增值服务,如账号认证、粉丝数据分析、精准推送、推广和营销工具等。这些服务帮助企业提升营销效果,同时也增加了微博的收入来源。 电商和导流:微博上有大量的电商导流业务,尤其是和明星、网红的合作推广。微博用户在浏览社交内容时,可以直接跳转到商品购买链接,微博通过这种方式赚取导流佣金。 游戏联运:微博也会与一些游戏公司合作推出联合运营的游戏,微博负责推广和流量引入,用户充值或付费时,微博可以获得一部分的分成。 这些模式相结合,使得微博能够在广告市场、内容创作和电商等多个领域获利。...
  5. 豪车的修理费用就是贵一些 去年买了保时捷卡宴SUV(Porsche Cayenne)后,我一直担心将来修车费用会很高。当时购车时,车厂做了一次全面保养,把车里里外外都清洁了一遍。虽然这辆车已经三年车龄,但看上去几乎和新车没区别。 在英国,三年以内的新车通常不需要做MOT年检。而且很多这类新车会通过PCP(个人合同购车)方式出租给车主。简单来说,就是车主每月支付一笔租金,租期通常为三年,期满后可以选择一次性付清尾款买下车辆,也可以继续换租一辆新车。 举个例子,如果一辆新车售价是10万英镑,车厂可能按未来三年折旧后的50%残值来计算每月租金。三年后,如果车主不想买断,车厂就会将车辆作为二手车卖出,回收那5万英镑的残值。这样一来,车厂基本不会亏钱。此外,PCP合同中还有附加条款,比如每年限行1万英里,超出的部分需要额外付费,这些内容都会写在合同里。 车龄到了三年,车辆需要首次做MOT年检,同时车辆的市场价值也会首次出现较大幅度的贬值(一般是50%,甚至更多)。修车厂老板告诉我,相比玛莎拉蒂等其他豪车,保时捷的保值率相对较高。 这一年我开这辆保时捷基本没出什么问题。今年年初做了年检,顺利通过。随后又做了一次常规保养,修车厂老板告诉我,前后刹车片已经磨损了80%–85%。我们住剑桥村里,开车比较多(上班、送娃、家庭旅游都要用车),一年大概能开1-2万英理。 几周后我将车送回去更换刹车片。修完后账单是将近900英镑。我觉得有点贵,车行老板解释说,不仅换了前后刹车片,还有一个前雷达的传感器掉进了车体内部,为了修这个传感器需要拆掉前保险杠等部件,花了6个小时人工费。 我当时质疑说为啥这次修这么贵,他说:“因为这是保时捷。”我说:“那和别的车有什么区别?”他说:“It is not the same.” 我说不都一样么,他说:“It is not...
  6. C++ 教程: 用std::move来移动所有权 📘 C++ 移动语义与 std::move() 教程 C++的std::move用于转移变量/对像的所有权/Ownership。 🔹 什么是移动语义? 在 C++ 中,移动语义通过转移资源所有权/Ownership(如内存或文件句柄)来优化性能,而不是复制它们。 移动语义是在 C++11 中引入的,它允许: 更快速地传递大型或昂贵的对象...
  7. C++ Ranges 教程 C++20 引入了 ranges(范围),这是一个强大且优雅的抽象,用于处理序列(如数组、vector 等)。相比传统的迭代器或旧式循环,Ranges 提高了代码的可读性、可组合性和性能。 什么是 Range? 在 C++20 中,range(范围) 是一种抽象,代表一个可以迭代的元素序列。它与 views(视图) 和 actions(操作) 如过滤、转换等配合使用非常自然。...
  8. 换了个奥迪Q5大灯花了我1000英镑 我那辆奥迪Q5 SUV今年年检没通过,原因是左前车灯坏了,需要更换。车厂告诉我,光是订购零件就要700多英镑,加上人工费,总费用得1000英镑。但没办法,如果不修,车辆年检(MOT)就过不了,车也不能上路。 MOT是英国的机动车强制性安全检测(Ministry of Transport Test)的简称。 近侧前位置灯不工作 drl/位置灯集成(4.2.1(a)(ii)) Nearside Front Position lamp not working drl/position...
❌