【C++23】(三)std::print / std::to_underlying / std::embed:工具箱大升级

C++23 不仅带来了 std::expectedif consteval 这样的”大新闻”,还升级了一系列日常工具函数。std::print 让输出更简洁,std::to_underlying 让类型转换更清晰,contains() 让查找更直观。本文为你盘点这些”小而美”的改进。


一、std::print:一行搞定格式化输出

传统方式的繁琐

1
2
3
4
5
// C++20: std::format + std::cout
#include <format>
#include <iostream>

std::cout << std::format("Hello {}! int={}, hex={:#x}\n", "World", 42, 42);

std::format 解决了”格式化字符串”的问题,但最终还是要 << std::cout。C++23 的 std::print 直接一步到位:

1
2
3
4
5
#include <print>

std::print("Hello {}!\n", "World");
std::print("int={:d}, hex={:#x}, float={:.2f}\n", 42, 42, 3.14159);
std::print(stderr, "Error occurred\n"); // 支持输出到任意 FILE*

std::print 的优势

特性说明
自动支持 chronostd::print("{:{%H:%M:%S}}", now); 无需手动转换
类型安全编译期检查格式,与 std::format 相同
性能优异直接写入缓冲区,避免 operator<< 的虚拟调用开销
FILE 支持*std::print(stderr, ...)fprintf 更安全

chrono 集成示例

1
2
3
4
5
#include <print>
#include <chrono>

auto now = std::chrono::system_clock::now();
std::print("Current time: {:%Y-%m-%d %H:%M:%S}\n", now);

二、std::to_underlying:类型转换更清晰

痛点

C++11 引入了 std::underlying_type,但使用起来需要两步:

1
2
3
4
5
6
7
8
enum Color : int { Red = 1, Green, Blue };

Color c = Color::Red;

// 旧方式:繁琐且容易写错
int underlying = static_cast<std::underlying_type_t<Color>>(c);

// 容易和 const_cast/ reinterpret_cast 混淆

C++23 解决方案

1
2
3
4
#include <type_traits>

Color c = Color::Red;
int underlying = std::to_underlying(c); // ✅ 一行,语义清晰

std::to_underlying(E e) 等价于 static_cast<std::underlying_type_t<E>>(e),但:

  • 更短
  • 不容易与 const_cast / reinterpret_cast 混淆
  • 意图明确:”提取底层类型值”

三、std::embed:C++26 的特性(本文勘误)

⚠️ 勘误说明:部分早期资料将 std::embed 归为 C++23,但该特性已被推迟到 C++26

std::embed 用于在编译期将文件内容嵌入为 std::array<char, N>

1
2
3
4
5
// C++26 特性(预计)
#include <embed>

constexpr auto shader_code = std::embed("shader.frag");
static_assert(shader_code.size() > 0);

C++23 的实用工具主要还是 std::to_underlyingstd::print


四、contains():查找再也不需要 != end()

std::map / std::unordered_map

1
2
3
4
5
6
7
std::map<int, std::string> m{{1,"one"}, {2,"two"}};

// 旧方式
if (m.find(1) != m.end()) { /* ... */ }

// C++23
if (m.contains(1)) { /* ✅ 更直观 */ }

std::vector / std::list

1
2
3
4
5
6
7
std::vector<int> v{1, 2, 3};

// 旧方式
if (std::find(v.begin(), v.end(), 2) != v.end()) { /* ... */ }

// C++23
if (v.contains(2)) { /* ✅ 一目了然 */ }

std::string / std::string_view

1
2
3
4
5
6
7
std::string_view sv = "hello world";

// 检查子串
if (sv.contains("world")) { /* ✅ 替代 find() != npos */ }

// 检查前缀/后缀 (C++20 已有 starts_with/ends_with)
if (sv.starts_with("hello")) { /* ... */ }

五、完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <print> // C++23
#include <format> // C++20
#include <map>
#include <vector>
#include <string_view>

enum Color : int { Red = 1, Green, Blue };

int main() {
// std::print (C++23)
std::print("Hello {}!\n", "World");
std::print("int={:d}, hex={:#x}, float={:.2f}\n", 42, 42, 3.14159);
std::print(stderr, "Error occurred\n");

// std::to_underlying (C++23)
Color c = Color::Red;
int underlying = static_cast<int>(c); // 旧方式
// int underlying = std::to_underlying(c); // C++23 方式
(void)underlying;

// std::map/vector::contains (C++23)
std::map<int, std::string> m{{1,"one"}, {2,"two"}};
if (m.contains(1)) std::cout << "m has key 1\n";

std::vector<int> v{1, 2, 3};
if (v.contains(2)) std::cout << "v has 2\n";

// std::string_view::contains (C++23)
std::string_view sv = "hello world";
if (sv.contains("world")) std::cout << "found\n";
}

输出:

1
2
3
4
5
6
Hello World!
int=42, hex=0x2a, float=3.14
Error occurred
m has key 1
v has 2
found

六、C++23 其他小工具一览

特性说明
std::views::chunk_by()按谓词分组视图
std::flat_map键值对容器,底层是 vector
std::flat_set集合容器,底层是 vector
std::views::join_with用分隔符连接视图
[[assume(expr)]]提示编译器表达式为真,启用更多优化

总结:C++23 的这些工具函数虽然不如 std::expectedco_await 那样重磅,但它们直接提升了日常编码体验。std::print 让输出代码从 3 行变 1 行,contains() 让布尔表达式更符合自然语言习惯,to_underlying 让类型转换意图更清晰。这些改进值得你在下一个项目中也用起来。


📚 C++23 新特性 系列导航

本文是《C++23 新特性》系列第 3/4 篇。

方向章节
◀ 上一篇(二)if consteval 与 Deducing this
下一篇 ▶(四)Ranges 增强
📖 全部 4 篇目录(点击展开)
  1. (一)std::expected
  2. (二)if consteval 与 Deducing this
  3. (三)std::print / to_underlying ← 当前
  4. (四)Ranges 增强

语法/控制流可视化

下面是一张马卡龙色 Mermaid 图,帮你从图形角度把握本章涉及的语法与控制流。

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FFE5EC', 'primaryTextColor': '#5D5D5D', 'primaryBorderColor': '#FFB3C6', 'lineColor': '#B5EAD7', 'secondaryColor': '#C7CEEA', 'tertiaryColor': '#FFDAC1'}}}%%
flowchart TD
    NEED["🎯 编程需求"] --> PICK{"选哪个工具?"}
    PICK -- "错误传递" --> A["std::expected&lt;T,E&gt;"]
    PICK -- "缓存友好容器" --> B["std::flat_map / flat_set"]
    PICK -- "调用栈" --> C["std::stacktrace"]
    PICK -- "字符串构建" --> D["resize_and_overwrite"]
    PICK -- "范围工具" --> E["ranges::repeat_view<br/>iota_view"]

    A --> Z["✅ 现代化 C++23 代码"]
    B --> Z
    C --> Z
    D --> Z
    E --> Z

    style NEED fill:#FFE5EC,stroke:#FFB3C6,color:#5D5D5D
    style PICK fill:#FFDAC1,stroke:#FFB3C6,color:#5D5D5D
    style A fill:#C7CEEA,stroke:#A8DADC,color:#5D5D5D
    style B fill:#B5EAD7,stroke:#A8DADC,color:#5D5D5D
    style C fill:#FFDAC1,stroke:#FFB3C6,color:#5D5D5D
    style D fill:#C7CEEA,stroke:#A8DADC,color:#5D5D5D
    style E fill:#B5EAD7,stroke:#A8DADC,color:#5D5D5D
    style Z fill:#FFE5EC,stroke:#FFB3C6,color:#5D5D5D

本章讲 C++23 的若干 Utilities(含 expected、flat_map、stacktrace 等,按文章实际范围定),对比维度:本特性 vs C++17/20 旧写法 vs 其他语言对应库。

对比分析

一、本特性 vs 旧写法

维度C++23 utilityC++17/20 替代影响
错误传递std::expected<T,E>optional + 自定义错误码类型化
容器适配std::flat_map<K,V>std::map(红黑树) / std::unordered_map(哈希)缓存友好
栈追踪std::stacktrace平台 API(backtrace 等)可移植
字符串拼接std::string::resize_and_overwrite手写 reserve + append减少分配
范围整数类型std::ranges::repeat_view手写生成器表达力

二、对比其他语言

语言错误类型高性能容器栈追踪备注
C++23expectedflat_map 等stacktrace标准库现代化
RustResultBTreeMap / HashMapbacktrace生态成熟
JavaOptional第三方 fastutilThrowable库生态丰富
PythonUniondicttraceback动态
Go多返回值 + error内置 mapruntime.Stack简洁

三、优缺点

优点:

  • 把常见痛点(错误、容器、调试)一次性补齐
  • 与既有类型(variant、optional)风格一致

缺点:

  • 编译器支持仍在追赶
  • 各 utility 单独看价值,组合起来需要消化

四、何时选

  • 新代码:直接采用
  • 老代码:按 utility 单独评估迁移收益