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

这一篇对应 demo:ref/cpp_demo/basics/cpp23_features/(强制 C++23 编译,库特性再用条件编译按标准库能力启用)。

这是「现代 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 增强 zip、enumerate、chunk…
42uz size_t 字面量,消除符号警告

编译器支持参差不齐——demo 的 CMake 要求 -std=c++23(GCC 14+ / Clang 18+ / Apple Clang 16+),在此之上再用 __has_include(<expected>) / __has_include(<print>) / __has_include(<generator>) 与 __cpp_explicit_this_parameter 做条件编译,标准库缺哪个就只打印说明。

demo(单文件 cpp23_features.cpp)真正跑代码的是 std::expected、deducing this、uz 字面量三项;if consteval、多维 []、std::to_underlying、Ranges 增强只打印文字说明或伪代码,std::print / std::generator 只做支持检测。下文这几节的代码是通用示意。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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);
}

try {
size_t pos;
int value = std::stoi(str, &pos);
if (pos != str.size()) {
return std::unexpected(ParseError::InvalidFormat);
}
if (value < 0 || value > 1000) {
return std::unexpected(ParseError::OutOfRange);
}
return value; // 成功:直接返回值
} catch (...) {
return std::unexpected(ParseError::InvalidFormat);
}
}

(demo 的 parse_int:空串 → EmptyInput,"abc" → InvalidFormat,"9999" → OutOfRange。)

方式 缺点
异常 控制流隐式、部分场景开销大
错误码 易忽略返回值
optional 只知失败,不知原因
expected 值 + 错误类型都明确

常用 API:

1
2
3
4
5
6
7
8
9
auto r = parse_int("42");
if (r) { std::cout << *r << "\n"; }
else { std::cout << to_string(r.error()) << "\n"; } // demo 自定义的 to_string(ParseError)

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

auto chain = parse_int("25")
.transform([](int v) { return v * v; }) // 625
.transform([](int v) { return std::to_string(v); }); // "625"

demo 还有一个 parse_and_sqrt:parse_int(str).transform(...) 把 expected<int, ParseError> 变成 expected<double, ParseError>,错误原样透传。

与 第 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];
}

编译器根据 self 是 T&、const T&、T&& 推导 const 性与值类别(demo 中 OldStyle / NewStyle 两个 struct 对照)。还可用于链式调用(demo 的 set_first(this auto&& self, int val))、递归 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
8
9
int simd_square(int n);  // 运行期优化实现,省略

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<std::size_t, 3, 4>> m(buf.data());
m[1, 2] = 3.14; // C++23 多维下标

配合 std::extents<IndexType, Extents...> 描述形状(第一个模板参数是下标类型),适合矩阵、图像块、科学计算。替代手写 buf[i * cols + j]。<mdspan> 需要较新的标准库(libstdc++ 15 / libc++ 17 起)。

八、其他实用改动

特性 示例 / 说明
42uz size_t 字面量,for (auto i = 0uz; i < v.size(); ++i)
std::to_underlying std::to_underlying(Color::Red) 取 enum 底层值(demo 里只留了注释,实际仍用 static_cast<std::underlying_type_t<Color>>)
std::flat_map / flat_set 连续内存有序关联容器,缓存友好
Ranges views::zip、enumerate、chunk、slide…(详见 第 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

程序(target cpp23_features)依次调用 demo_expected()、demo_deducing_this()、demo_misc_features()、demo_ranges_enhancements(),最后 demo_feature_summary() 打印当前编译器对 std::expected、std::print、Deducing this、std::generator、size_t literal (uz) 五项的 ✅/❌ 支持情况,以及 __cplusplus 的值。

特性 大致要求
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

demo 的 CMakeLists.txt 设置了 CMAKE_CXX_STANDARD 23 + CMAKE_CXX_STANDARD_REQUIRED ON,编译器不支持 C++23 时会直接报错,不再静默回退到 C++20。至少需要 GCC 14+ / Clang 18+ / Apple Clang 16+;系列 Docker 镜像默认的 GCC 15 可以直接编译,也可以用 ./build.sh --cxx clang++ 切到 Clang 22 + libc++ 对比两套标准库的支持情况。

策略:新代码用 expected + print;需要兼容旧编译器的库代码,可以像 demo 那样用 #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 ✅