C++23 不仅带来了 std::expected、if consteval 这样的”大新闻”,还升级了一系列日常工具函数。std::print 让输出更简洁,std::to_underlying 让类型转换更清晰,contains() 让查找更直观。本文为你盘点这些”小而美”的改进。
一、std::print:一行搞定格式化输出
传统方式的繁琐
1 2 3 4 5
| #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");
|
std::print 的优势
| 特性 | 说明 |
|---|
| 自动支持 chrono | std::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);
|
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
| #include <embed>
constexpr auto shader_code = std::embed("shader.frag"); static_assert(shader_code.size() > 0);
|
C++23 的实用工具主要还是 std::to_underlying 和 std::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()) { }
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()) { }
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")) { }
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> #include <format> #include <map> #include <vector> #include <string_view>
enum Color : int { Red = 1, Green, Blue };
int main() { std::print("Hello {}!\n", "World"); std::print("int={:d}, hex={:#x}, float={:.2f}\n", 42, 42, 3.14159); std::print(stderr, "Error occurred\n"); Color c = Color::Red; int underlying = static_cast<int>(c); (void)underlying; 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 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::expected、co_await 那样重磅,但它们直接提升了日常编码体验。std::print 让输出代码从 3 行变 1 行,contains() 让布尔表达式更符合自然语言习惯,to_underlying 让类型转换意图更清晰。这些改进值得你在下一个项目中也用起来。
📚 C++23 新特性 系列导航
本文是《C++23 新特性》系列第 3/4 篇。
📖 全部 4 篇目录(点击展开)
- (一)std::expected
- (二)if consteval 与 Deducing this
- (三)std::print / to_underlying ← 当前
- (四)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<T,E>"]
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 utility | C++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++23 | expected | flat_map 等 | stacktrace | 标准库现代化 |
| Rust | Result | BTreeMap / HashMap | backtrace | 生态成熟 |
| Java | Optional | 第三方 fastutil | Throwable | 库生态丰富 |
| Python | Union | dict | traceback | 动态 |
| Go | 多返回值 + error | 内置 map | runtime.Stack | 简洁 |
三、优缺点
优点:
- 把常见痛点(错误、容器、调试)一次性补齐
- 与既有类型(variant、optional)风格一致
缺点:
- 编译器支持仍在追赶
- 各 utility 单独看价值,组合起来需要消化
四、何时选
- 新代码:直接采用
- 老代码:按 utility 单独评估迁移收益