C++20 上了 Concepts、协程、Ranges;C++23 继续「还债」:std::expected 补错误处理、std::print 补输出、deducing this 补成员函数模板、std::generator 补协程生成器——不必等 C++26 也能用上不少干货。

这一篇对应 demo:ref/cpp_demo/basics/cpp23_features/(含条件编译,按编译器能力启用特性)。

这是「现代 C++ 实战」系列的第 17 篇。建议先读 第 16 篇:GoogleTest第 08 篇:错误处理

一、C++23 概览

特性 解决什么
std::expected<T, E> 成功值 + 错误原因,替代部分异常/错误码
deducing this 一份成员函数覆盖 const/非 const
std::print / println 类型安全格式化输出
std::generator 标准协程生成器(惰性序列)
if consteval constexpr 函数里区分编译期/运行期
std::mdspan、多维 [] 多维数组非拥有视图
Ranges 增强 zipenumeratechunk
42uz size_t 字面量,消除符号警告

编译器支持参差不齐——demo 用 __has_include 与特性宏做条件编译;GCC 13+、Clang 17+ 起步,部分特性需 GCC 14 / Clang 18。

二、std::expected:Result 的 C++ 版

类似 Rust 的 Result<T, E>:要么有值 T,要么有错误 E

1
2
3
4
5
6
7
8
9
10
#include <expected>

enum class ParseError { EmptyInput, InvalidFormat, OutOfRange };

std::expected<int, ParseError> parse_int(const std::string& str) {
if (str.empty())
return std::unexpected(ParseError::EmptyInput);
// …解析成功 return value; 失败 return std::unexpected(err);
return 42;
}
方式 缺点
异常 控制流隐式、部分场景开销大
错误码 易忽略返回值
optional 只知失败,不知原因
expected 值 + 错误类型都明确

常用 API:

1
2
3
4
5
6
7
8
9
auto r = parse_int("42");
if (r) { use(*r); }
else { log(r.error()); }

int safe = parse_int("x").value_or(-1);

parse_int("25")
.transform([](int v) { return v * v; })
.transform([](int v) { return std::to_string(v); });

第 08 篇 对照:expected 适合可预期的失败(解析、文件打开);异常仍适合真正异常路径

三、deducing this:显式 this 参数

以前 const / 非 const 各写一份:

1
2
int& at(size_t i) { return data[i]; }
const int& at(size_t i) const { return data[i]; }

C++23 deducing this 合并为一份:

1
2
3
4
template <typename Self>
auto&& at(this Self&& self, size_t i) {
return std::forward<Self>(self).data[i];
}

编译器根据 selfT&const T&T&& 推导 const 性与值类别。还可用于链式调用递归 lambda

1
2
3
auto factorial = [](this auto&& self, int n) -> int {
return n <= 1 ? 1 : n * self(n - 1);
};

__cpp_explicit_this_parameter >= 202110L(GCC 14+、Clang 18+ 较完整)。

四、std::print / std::println

终于不必 cout << 链式拼接:

1
2
3
4
#include <print>

std::print("Hello, {}!\n", name);
std::println("x = {}, y = {}", x, y); // 自动换行

基于 std::format第 09 篇),类型安全、可本地化。头文件 <print>,GCC 14+ / Clang 18+ 库支持较新。

五、std::generator:协程生成器

C++20 有协程,但缺标准「生成器」类型;C++23 补上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <generator>

std::generator<int> fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a;
auto next = a + b;
a = b;
b = next;
}
}

for (int n : fibonacci()) {
if (n > 1000) break;
std::print("{} ", n);
}

惰性:按需 co_yield,适合大序列、管道式处理。编译器与 <generator> 头文件支持仍在完善中。

六、if consteval:编译期 vs 运行期

constexpr 函数里同一签名、两套实现

1
2
3
4
5
6
7
constexpr int heavy_or_simple(int n) {
if consteval {
return n * n; // 编译期:简单实现
} else {
return simd_square(n); // 运行期:SIMD 优化
}
}

if consteval 分支在编译期求值时必须可走 constexpr 路径;比 if (std::is_constant_evaluated()) 语义更清晰。

七、std::mdspan 与多维 []

多维数组的非拥有视图(不拷贝数据):

1
2
3
4
5
#include <mdspan>

std::vector<double> buf(12);
std::mdspan<double, std::extents<3, 4>> m(buf.data());
m[1, 2] = 3.14; // C++23 多维下标

配合 std::extents 描述形状,适合矩阵、图像块、科学计算。替代手写 buf[i * cols + j]

八、其他实用改动

特性 示例 / 说明
42uz size_t 字面量,for (auto i = 0uz; i < v.size(); ++i)
std::to_underlying std::to_underlying(Color::Red) 取 enum 底层值
std::flat_map / flat_set 连续内存有序关联容器,缓存友好
Ranges views::zipenumeratechunkslide…(详见 第 10 篇
1
2
3
// enumerate — 类似 Python
for (auto [idx, val] : vec | std::views::enumerate)
std::print("{}: {}\n", idx, val);

九、demo 运行与编译器要求

1
2
cd ref/cpp_demo/basics/cpp23_features
./build.sh --run

程序末尾会打印当前编译器对 expectedprint、deducing this、generatoruz 等的 ✅/❌ 支持情况。

特性 大致要求
std::expected GCC 13+、Clang 17+
std::print、deducing this GCC 14+、Clang 18+
std::generator 较新 libc++ / libstdc++
全量 C++23 -std=c++23__cplusplus == 202302L

策略:新代码用 expected + print;旧编译器用 #if __has_include(<expected>) 回退到 optional 或错误码。

十、小结

特性 一句话
expected 值或错误,可链式 transform
deducing this 一份成员函数,const/非 const 通吃
print format 风格的标准输出
generator 协程惰性序列
if consteval constexpr 内外分支
mdspan 多维数组视图

现代 C++ 实战系列第 17 篇完。下一篇进入第三季:排序算法与 std::sort

系列导航

篇号 标题 状态
16 GoogleTest 单元测试
17 C++23 新特性(本篇)
18 排序算法与 std::sort 下一篇

完整大纲见工作区 docs/CPP_SERIES_OUTLINE.md