C++23 新特性
C++23新特性详解:std::print、std::expected、std::flat_map、deducing this、std::mdspan、std::generator 等。
前置知识
- C++20 概念:建议先完成前一篇的学习
学习目标
- 掌握「1. 历史动机与发展脉络」的核心机制、典型用法与常见陷阱
- 掌握「2. 形式化定义」的核心机制、典型用法与常见陷阱
- 掌握「3. 理论推导与原理解析」的核心机制、典型用法与常见陷阱
- 掌握「4. 代码示例(企业级 production-ready)」的核心机制、典型用法与常见陷阱
- 掌握「5. 对比分析」的核心机制、典型用法与常见陷阱
1. 历史动机与发展脉络
1.1 C++23 标准的定位
C++23 是继 C++20 之后的”完善版”标准。C++20 引入了 concepts、ranges、coroutines、modules 等大型特性,C++23 旨在:
- 完善 C++20:修复 concepts 与 ranges 的不完善之处;
- 填补库空白:
std::expected、std::flat_map、std::generator等长期被社区要求的工具; - 简化常用模式:
std::print/std::println、if consteval、多维下标等; - 性能优化:
std::move_only_function、std::mdspan等。
C++23 于 2023 年完成技术定稿,以 ISO/IEC 14882:2024 的名义于 2024 年正式发布(2023 年是「定稿年份」,官方出版编号是 14882:2024)。
1.2 关键提案一览
| 提案 | 标题 | 作者 | 特性 |
|---|---|---|---|
| P2093 | std::print formatting facility | V. Zverovich | std::print/std::println |
| P0323 | std::expected | V. Botet Escriba et al. | expected 类型 |
| P0429 | std::flat_map | M. Park, B. Yahyaouy | 扁平映射容器 |
| P1222 | std::flat_set | M. Park | 扁平集合 |
| P2295 | Support for UTF-8 as a portable source file encoding | Corentin Jabot | UTF-8 源码 |
| P0847 | Deducing this | Gašper Ažman et al. | 显式对象参数 |
| P1938 | if consteval | Ed Catmur | consteval 判断 |
| P1169 | static operator() | G. Ažman, B. Revzin | 静态调用运算符 |
| P0009 | std::mdspan | H. Carter Edwards et al. | 多维视图 |
| P2502 | std::generator | Casey Carter | 协程生成器 |
| P0288 | std::move_only_function | R. Leahy | 可移动函数包装 |
| P2447 | std::span constructors | A. Krinkin | span 改进 |
| P2164 | views::enumerate | Tim Song | 索引视图 |
| P2286 | Formatting ranges | B. Stroustrup | ranges 格式化 |
| P1679 | std::string::contains | — | 字符串包含 |
| P2465 | Standard Library Modules std/std.compat | — | import std;(编译器落地滞后) |
| P0881 | std::stacktrace | — | 栈回溯 |
1.3 C++11/14/17/20/23/26 演进
| 标准 | 发布年 | 关键特性 |
|---|---|---|
| C++11 | 2011 | auto、lambda、move semantics、concurrency、smart pointers |
| C++14 | 2014 | generic lambda、std::make_unique、constexpr 改进 |
| C++17 | 2017 | std::optional、std::variant、std::string_view、if constexpr |
| C++20 | 2020 | concepts、ranges、coroutines、modules、<=> |
| C++23 | 2024(ISO/IEC 14882:2024) | std::print、std::expected、std::flat_map、std::mdspan、deducing this |
| C++26 | 草案(预计 2026 年底前后发布) | 静态反射、契约、std::execution、pack indexing、std::inplace_vector、std::hive |
1.4 与其他语言对比
| 特性 | C++23 | Rust 1.70 | Swift 5.9 | Java 21 | Python 3.12 |
|---|---|---|---|---|---|
| 模式匹配结果类型 | std::expected<T,E> | Result<T,E> | Result<T,E> | Optional<T> | try/except |
| 格式化输出 | std::print | println! | print() | System.out.println | print() |
| 多维视图 | std::mdspan | ndarray (crate) | Array<T> | JOML | numpy |
| 协程生成器 | std::generator<T> | impl Iterator | AsyncStream | Stream<T> | generator |
| 静态调用 | static operator() | Fn trait | static func | static method | @staticmethod |
2. 形式化定义
2.1 std::expected<T, E> 的形式化
std::expected<T, E> 表示可能成功(含 T)或失败(含 E)的运算结果。形式化定义:
API 简化:
template <typename T, typename E>
class expected {
bool has_value_;
union {
T value_;
E error_;
};
public:
expected(const T&); // 成功构造
expected(unexpected<E>); // 失败构造
bool has_value() const noexcept;
operator bool() const noexcept;
T& value() &; // 取值或抛异常
const T& value() const &;
E& error() &; // 取错误
T value_or(U&&) const &; // 取值或默认
T& operator*() &; // 解引用
T* operator->();
};
2.2 std::mdspan 的形式化
std::mdspan<T, Extents, LayoutPolicy, AccessorPolicy> 是多维数组视图:
Extents:维度信息,如std::extents<size_t, 3, 4, 5>;LayoutPolicy:布局策略(layout_right/layout_left/layout_stride);AccessorPolicy:访问策略(默认default_accessor)。
2.3 Deducing this 的形式化
传统成员函数的 this 类型固定为 T* 或 const T*,无法对 cv-qualifier 或引用类别重载。C++23 允许显式声明 this 参数:
struct C {
void f(this C& self); // 等价于 void f();
void f(this const C& self); // 等价于 void f() const;
void f(this C&& self); // 右值 this
void f(this C self); // 按值 this
};
形式化推导规则:
2.4 if consteval 的形式化
C++23 引入 if consteval 替代 if constexpr (std::is_constant_evaluated()):
// C++20 风格
if constexpr (std::is_constant_evaluated()) { /* 编译期 */ }
else { /* 运行时 */ }
// C++23 风格
if consteval { /* 编译期 */ }
else { /* 运行时 */ }
形式化语义:
注意:if consteval 仅在 consteval 上下文或 constexpr 函数中生效。
2.5 std::generator<T> 的形式化
std::generator<T> 是 C++23 协程生成器:
API 简化:
template <typename Ref, typename V = void, typename Allocator = void>
class generator {
using yielded = /* see spec */;
public:
generator() noexcept = default;
generator(generator&& other) noexcept;
generator& operator=(generator&& other) noexcept;
struct iterator {
yielded operator*() const;
iterator& operator++();
};
iterator begin();
iterator end();
};
std::generator 满足 input_iterator 概念,但不可拷贝。
3. 理论推导与原理解析
3.1 std::expected 与 monadic 错误处理
std::expected 支持 monadic 操作(and_then、or_else、transform),允许链式组合:
std::expected<int, Error> parse(const std::string&);
std::expected<int, Error> validate(int);
std::expected<std::string, Error> format(int);
auto result = parse(input)
.and_then(validate)
.and_then([](int v) { return format(v); });
形式化:
类似 Haskell 的 >>= 操作符。
3.2 std::flat_map 的复杂度分析
std::flat_map<K, V> 内部使用排序的连续容器(默认 std::vector):
- 查找:(二分查找);
- 插入:(最坏,需移动元素);
- 删除:;
- 遍历:,缓存友好;
- 内存:连续存储,无节点开销。
对比 std::map<K, V>(红黑树):
- 查找:;
- 插入:;
- 遍历:,缓存不友好(节点分散);
- 内存:每个节点额外
3指针 + 颜色位。
适用场景:
flat_map:小数据量、读多写少、需要缓存友好;map:大数据量、频繁插入删除。
3.3 Deducing this 实现原理
传统成员函数:
struct String {
char* data_;
size_t size() const { /* ... */ }
};
// 编译器内部展开为:size(const String* this)
Deducing this:
struct String {
char* data_;
template <typename Self>
size_t size(this Self&& self) {
// self 的类型根据调用对象的 cv/引用类别推导
return /* ... */;
}
};
可实现 cv 与引用类别的统一模板,简化库代码:
struct String {
char* data_;
size_t size_ = 0;
template <typename Self>
decltype(auto) data(this Self&& self) {
return std::forward_like<Self>(self.data_);
}
};
String s;
const String cs;
String makeRvalue();
s.data(); // Self = String&,返回 char*
cs.data(); // Self = const String&,返回 const char*
makeRvalue().data(); // Self = String,返回 char* (右值引用)
3.4 std::mdspan 与 BLAS 集成
std::mdspan 提供多维数组视图,可与 BLAS 等数值库无缝集成:
#include <mdspan>
void matrix_multiply(
std::mdspan<const double, std::dextents<size_t, 2>> A,
std::mdspan<const double, std::dextents<size_t, 2>> B,
std::mdspan<double, std::dextents<size_t, 2>> C)
{
for (int i = 0; i < A.extent(0); ++i)
for (int j = 0; j < B.extent(1); ++j) {
double sum = 0;
for (int k = 0; k < A.extent(1); ++k)
sum += A[i, k] * B[k, j];
C[i, j] = sum;
}
}
// 使用
double a_data[6] = {1, 2, 3, 4, 5, 6};
double b_data[6] = {1, 0, 0, 1, 1, 1};
double c_data[4] = {0, 0, 0, 0};
auto A = std::mdspan(a_data, 2, 3);
auto B = std::mdspan(b_data, 3, 2);
auto C = std::mdspan(c_data, 2, 2);
matrix_multiply(A, B, C);
// C = [[1*1+2*0+3*1, 1*0+2*1+3*1],
// [4*1+5*0+6*1, 4*0+5*1+6*1]]
// = [[4, 5], [10, 11]]
3.5 std::generator 协程原理
std::generator 基于 C++20 协程,简化迭代器实现:
std::generator<int> range(int start, int stop) {
for (int i = start; i < stop; ++i) {
co_yield i;
}
}
for (int x : range(0, 5)) {
std::print("{} ", x); // 输出:0 1 2 3 4
}
协程原理:
- 编译器将
range转换为状态机对象; co_yield i挂起协程并返回i;begin()/++iter恢复协程执行;- 协程结束时返回,迭代器变为
end()状态。
3.6 ranges 增强的算法
C++23 增强了 ranges 库,新增:
views::enumerate:带索引遍历;views::zip:并行遍历多个范围;views::adjacent:相邻元素滑动窗口;views::chunk:分块;views::slide:滑动窗口;ranges::to:将视图转换为容器。
#include <ranges>
#include <vector>
#include <print>
int main() {
std::vector vec = {10, 20, 30, 40};
// 带索引遍历
for (auto [idx, val] : vec | std::views::enumerate) {
std::print("[{}] = {}\n", idx, val);
}
// 分块
for (auto chunk : vec | std::views::chunk(2)) {
for (auto x : chunk) std::print("{} ", x);
std::print("\n");
}
// 转换为容器
auto squared = vec
| std::views::transform([](int x) { return x * x; })
| std::ranges::to<std::vector>();
return 0;
}
4. 代码示例(企业级 production-ready)
4.1 std::print 与格式化
// file: print_demo.cpp
// compile: g++ -std=c++23 -O2 -o print_demo print_demo.cpp
#include <print>
#include <vector>
#include <string>
#include <chrono>
int main() {
// 基本用法
std::print("Hello, {}!\n", "World");
std::println("Value: {}", 42);
std::println("Pi: {:.4f}", 3.14159265);
// 输出到指定流
std::print(stderr, "Error: {}\n", "file not found");
// 格式化容器
std::vector<int> v = {1, 2, 3, 4, 5};
std::println("Vector: {}", v); // C++23 起支持
// 格式化字符串
std::string name = "Alice";
int age = 30;
std::println("Name: {:>10}, Age: {:03d}", name, age);
// 时间格式化
auto now = std::chrono::system_clock::now();
std::println("Now: {:%Y-%m-%d %H:%M:%S}", now);
return 0;
}
4.2 std::expected 错误处理
// file: expected_demo.cpp
// compile: g++ -std=c++23 -O2 -o expected expected_demo.cpp
#include <expected>
#include <string>
#include <print>
#include <cmath>
enum class MathError {
DivisionByZero,
NegativeSqrt,
};
std::expected<double, MathError> divide(double a, double b) {
if (b == 0) return std::unexpected(MathError::DivisionByZero);
return a / b;
}
std::expected<double, MathError> sqrt_safe(double x) {
if (x < 0) return std::unexpected(MathError::NegativeSqrt);
return std::sqrt(x);
}
// 链式调用:复合两个可能失败的函数
std::expected<double, MathError> compute(double a, double b) {
return divide(a, b)
.and_then([](double x) { return sqrt_safe(x); });
}
std::string to_string(MathError e) {
switch (e) {
case MathError::DivisionByZero: return "Division by zero";
case MathError::NegativeSqrt: return "Negative square root";
}
return "Unknown error";
}
int main() {
auto r1 = compute(16.0, 4.0); // sqrt(4) = 2
auto r2 = compute(16.0, 0.0); // 失败:除零
auto r3 = compute(-16.0, 4.0); // 失败:负平方根
if (r1) std::println("r1 = {}", *r1);
else std::println("r1 error: {}", to_string(r1.error()));
if (r2) std::println("r2 = {}", *r2);
else std::println("r2 error: {}", to_string(r2.error()));
if (r3) std::println("r3 = {}", *r3);
else std::println("r3 error: {}", to_string(r3.error()));
// 使用 value_or
auto safe = compute(-16.0, 4.0).value_or(-1.0);
std::println("safe = {}", safe);
return 0;
}
4.3 std::flat_map 使用
// file: flat_map_demo.cpp
// compile: g++ -std=c++23 -O2 -o flat_map flat_map_demo.cpp
#include <flat_map>
#include <flat_set>
#include <string>
#include <print>
int main() {
std::flat_map<std::string, int> scores;
scores["Alice"] = 95;
scores["Bob"] = 87;
scores["Carol"] = 92;
// 排序遍历(key 有序)
for (const auto& [name, score] : scores) {
std::println("{}: {}", name, score);
}
// 查找
if (auto it = scores.find("Bob"); it != scores.end()) {
std::println("Found Bob: {}", it->second);
}
// flat_set
std::flat_set<int> nums = {5, 1, 3, 4, 2};
for (int n : nums) {
std::print("{} ", n); // 输出:1 2 3 4 5
}
std::print("\n");
return 0;
}
4.4 Deducing this 实现链式调用
// file: deducing_this.cpp
// compile: g++ -std=c++23 -O2 -o dt deducing_this.cpp
#include <utility>
#include <print>
class StringBuilder {
std::string data_;
public:
// 通过 deducing this 实现链式调用,支持 const、&、&& 三种情况
template <typename Self>
StringBuilder& append(this Self&& self, std::string_view s) {
self.data_.append(s);
return self;
}
// 通用 const 访问
template <typename Self>
decltype(auto) str(this Self&& self) {
return std::forward_like<Self>(self.data_);
}
};
int main() {
StringBuilder b;
b.append("Hello").append(", ").append("World!");
std::println("{}", b.str());
const StringBuilder cb;
// cb.append("test"); // 编译错误:const 对象无法调用非 const append
return 0;
}
4.5 std::mdspan 多维数组
// file: mdspan_demo.cpp
// compile: g++ -std=c++23 -O2 -o mdspan mdspan_demo.cpp
#include <mdspan>
#include <vector>
#include <print>
#include <numeric>
int main() {
// 2D 矩阵
std::vector<double> data(3 * 4);
std::iota(data.begin(), data.end(), 0.0);
std::mdspan mat(data.data(), 3, 4);
std::println("Matrix {}x{}:", mat.extent(0), mat.extent(1));
for (size_t i = 0; i < mat.extent(0); ++i) {
for (size_t j = 0; j < mat.extent(1); ++j) {
std::print("{:6.1f} ", mat[i, j]);
}
std::print("\n");
}
// 3D 张量
std::vector<int> tensor_data(2 * 3 * 4);
std::iota(tensor_data.begin(), tensor_data.end(), 0);
std::mdspan tensor(tensor_data.data(), 2, 3, 4);
std::println("\nTensor {}x{}x{}:", tensor.extent(0), tensor.extent(1), tensor.extent(2));
for (size_t i = 0; i < tensor.extent(0); ++i) {
std::println("Slice {}:", i);
for (size_t j = 0; j < tensor.extent(1); ++j) {
for (size_t k = 0; k < tensor.extent(2); ++k) {
std::print("{:3d} ", tensor[i, j, k]);
}
std::print("\n");
}
}
return 0;
}
4.6 std::generator 协程生成器
// file: generator_demo.cpp
// compile: g++ -std=c++23 -O2 -o gen generator_demo.cpp
#include <generator>
#include <print>
// 斐波那契数列生成器
std::generator<int> fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a;
auto tmp = a + b;
a = b;
b = tmp;
}
}
// 有限范围生成器
std::generator<int> range(int start, int stop, int step = 1) {
for (int i = start; i < stop; i += step) {
co_yield i;
}
}
// 嵌套生成器
std::generator<int> flatten(std::vector<std::vector<int>> nested) {
for (auto& inner : nested) {
for (int x : inner) {
co_yield x;
}
}
}
int main() {
// 斐波那契前 10 项
int count = 0;
for (int x : fibonacci()) {
if (count++ >= 10) break;
std::print("{} ", x);
}
std::print("\n");
// 范围
for (int x : range(0, 20, 3)) {
std::print("{} ", x); // 0 3 6 9 12 15 18
}
std::print("\n");
// 嵌套扁平化
std::vector<std::vector<int>> nested = {{1, 2}, {3, 4, 5}, {6}};
for (int x : flatten(std::move(nested))) {
std::print("{} ", x); // 1 2 3 4 5 6
}
std::print("\n");
return 0;
}
4.7 std::move_only_function
// file: move_only_function.cpp
// compile: g++ -std=c++23 -O2 -o mof move_only_function.cpp
#include <functional>
#include <memory>
#include <print>
int main() {
// std::function 要求可拷贝,std::move_only_function 放宽此限制
std::move_only_function<int(int)> f;
// 包装 move-only lambda(捕获 unique_ptr)
auto ptr = std::make_unique<int>(42);
f = [p = std::move(ptr)](int x) { return *p + x; };
std::println("Result: {}", f(8)); // 输出 50
// 移动 f
auto g = std::move(f);
std::println("After move: {}", g(8));
return 0;
}
4.8 if consteval 与 static operator()
// file: consteval_static.cpp
// compile: g++ -std=c++23 -O2 -o cs consteval_static.cpp
#include <print>
#include <cstdint>
#include <bit>
constexpr uint32_t hash_string(const char* s) {
if consteval {
// 编译期:使用复杂算法
uint32_t hash = 5381;
while (*s) hash = ((hash << 5) + hash) + *s++;
return hash;
} else {
// 运行时:使用更优化的算法
uint32_t hash = 5381;
while (*s) hash = hash * 33 + *s++;
return hash;
}
}
// 静态调用运算符
struct Hasher {
static int operator()(int x) { // C++23
return x * 31;
}
};
int main() {
constexpr uint32_t h1 = hash_string("hello"); // 编译期
uint32_t h2 = hash_string("world"); // 运行时
std::println("h1 = {}", h1);
std::println("h2 = {}", h2);
// 静态调用运算符(无需实例化对象)
int result = Hasher{}(42);
std::println("Hasher result: {}", result);
return 0;
}
4.9 views::enumerate 与 views::zip
// file: ranges_enhanced.cpp
// compile: g++ -std=c++23 -O2 -o re ranges_enhanced.cpp
#include <ranges>
#include <vector>
#include <string>
#include <print>
int main() {
std::vector<std::string> names = {"Alice", "Bob", "Carol"};
std::vector<int> ages = {30, 25, 28};
// 带索引遍历
for (auto [idx, name] : names | std::views::enumerate) {
std::println("[{}] {}", idx, name);
}
// 并行遍历
for (auto [name, age] : std::views::zip(names, ages)) {
std::println("{} is {}", name, age);
}
// 相邻元素
std::vector<int> nums = {1, 2, 3, 4, 5};
for (auto [a, b] : nums | std::views::adjacent<2>) {
std::println("{} + {} = {}", a, b, a + b);
}
// 滑动窗口
for (auto window : nums | std::views::slide(3)) {
for (int x : window) std::print("{} ", x);
std::print("\n");
}
// 转换为容器
auto doubled = nums
| std::views::transform([](int x) { return x * 2; })
| std::ranges::to<std::vector>();
std::println("Doubled: {}", doubled);
return 0;
}
4.10 CMake 项目示例
# CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(cpp23_demo CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# GCC 13+ 或 Clang 17+ 或 MSVC 19.32+ 支持 C++23
add_executable(print_demo print_demo.cpp)
add_executable(expected_demo expected_demo.cpp)
add_executable(flat_map_demo flat_map_demo.cpp)
add_executable(mdspan_demo mdspan_demo.cpp)
add_executable(generator_demo generator_demo.cpp)
# 启用实验性库(部分编译器需要)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(print_demo PRIVATE -fexperimental-library)
target_compile_options(mdspan_demo PRIVATE -fexperimental-library)
endif()
4.11 std::span 改进
// file: span_improved.cpp
// C++23 改进了 std::span 的构造
#include <span>
#include <vector>
#include <print>
void process(std::span<const int> data) {
for (int x : data) std::print("{} ", x);
std::print("\n");
}
int main() {
// C++23:从初始化列表构造 span
process({1, 2, 3, 4, 5}); // C++23 起合法
std::vector<int> v = {10, 20, 30};
process(v);
return 0;
}
4.12 std::stacktrace 栈回溯
C++23 把栈回溯标准化为 std::stacktrace(提案 P0881),异常诊断、崩溃日志不再依赖平台专有 API:
// file: stacktrace_demo.cpp
// compile: g++ -std=c++23 -O0 -g -o st stacktrace_demo.cpp -lstdc++exp
// 注意:GCC/libstdc++ 上 <stacktrace> 位于 libstdc++exp 库,需显式链接
#include <stacktrace>
#include <print>
void inner() {
// 打印当前调用栈(调试用途;注意打印本身有运行时开销)
std::println("{}\n", std::stacktrace::current());
}
void outer() { inner(); }
int main() {
outer();
return 0;
}
// 典型输出(地址随平台变化):
// 0# inner() at stacktrace_demo.cpp:7
// 1# outer() at stacktrace_demo.cpp:10
// 2# main at ...
4.13 import std;:标准库模块(落地滞后)
C++23 把整个标准库封装成模块 std 与 std.compat(提案 P2465),理论上只需写:
import std; // 一次性导入整个标准库(不再需要 #include)
int main() {
std::println("hello, modules");
}
但要客观说明:import std 的编译器/构建系统落地长期滞后于标准。它要求编译器以二进制模块接口(BMI)形式预构建标准库,GCC 15、Clang(libc++)与 MSVC 的支持进度各不相同,CMake 也需要较新版本才能简化配置。生产环境中更稳妥的做法仍是 #include 头文件,或在确认工具链完整支持后再切换。这本身就是 C++23 的一个缩影:标准已定稿,生态落地需要时间。
5. 对比分析
5.1 std::expected 与 Rust Result
| 维度 | std::expected<T,E> | Rust Result<T,E> |
|---|---|---|
| 引入时间 | C++23 | Rust 1.0 |
| 模式匹配 | if (e) ... else e.error() | match e { Ok(v) => ..., Err(e) => ... } |
? 操作符 | 无 | 有(早期返回错误) |
| monadic 操作 | and_then/or_else/transform | and_then/or/map |
| 性能 | 与 Rust 相当(无堆分配) | 零开销 |
| 与异常 | 可共存 | 与 panic! 分离 |
5.2 std::flat_map 与其他语言对应
| 语言 | 等价容器 | 复杂度(查找) |
|---|---|---|
| C++ | std::flat_map (C++23) | |
| C++ | std::map | |
| Rust | BTreeMap | |
| Java | TreeMap | |
| Python | dict | (哈希) |
| Go | map | (哈希) |
5.3 std::generator 与其他语言协程
| 语言 | 协程类型 | 关键字 |
|---|---|---|
| C++23 | std::generator<T> | co_yield |
| Python | Generator[T] | yield |
| C# | IAsyncEnumerable<T> | yield return |
| JavaScript | AsyncGenerator | yield |
| Rust | impl Iterator (sync) / impl Stream (async) | yield(仅 nightly) |
| Kotlin | Sequence<T> | yield |
5.4 编译器支持矩阵
下表为大致情况(不同标准库实现、不同特性的落地时间差异很大),精确版本请以 cppreference 编译器支持页 为准:
| 特性 | GCC | Clang | MSVC |
|---|---|---|---|
std::print/println | 14+ | 17+ | 19.34+ |
std::expected | 12+ | 16+ | 19.33+ |
std::flat_map/flat_set | 14+ | 17+ | 19.37+ |
std::mdspan | 14+ (-fexperimental-library) | 17+ | 19.32+ |
std::generator | 14+ (-fexperimental-library) | 17+ (-std=c++23) | 19.34+ |
| deducing this | 14+ | 18+ | 19.32+ |
if consteval | 12+ | 15+ | 19.32+ |
static operator() | 13+ | 16+ | 19.32+ |
std::move_only_function | 12+ | 17+ | 19.32+ |
views::enumerate | 13+ | 17+ | 19.32+ |
ranges::to | 14+ | 17+ | 19.32+ |
std::span 初始化列表构造 | 12+ | 15+ | 19.32+ |
6. 常见陷阱与最佳实践
6.1 陷阱 1:std::expected 与异常混用
std::expected<int, Error> divide(int a, int b) {
if (b == 0) return std::unexpected(Error::DivByZero);
return a / b;
}
// 反例:在 expected 内部抛异常
int risky() {
auto result = divide(10, 0);
return result.value(); // 抛 std::bad_expected_access
}
最佳实践:明确选择错误处理策略(异常 or expected),不要混用。
6.2 陷阱 2:std::flat_map 频繁插入性能差
std::flat_map<int, std::string> map;
for (int i = 0; i < 100000; ++i) {
map[i] = std::to_string(i); // 每次插入 O(n) 移动!
}
最佳实践:批量插入时先收集所有元素再 sort + unique,或使用 std::map。
6.3 陷阱 3:deducing this 的递归问题
struct C {
template <typename Self>
void f(this Self&& self) {
self.f(); // 无限递归!
}
};
最佳实践:避免在 deducing this 函数中调用同名函数,或在签名中明确类型。
6.4 陷阱 4:std::generator 协程未恢复
std::generator<int> bad() {
co_yield 1;
co_yield 2;
// 协程未结束就提前返回
}
auto gen = bad();
auto it = gen.begin();
++it;
// 此时若 gen 析构,协程会被强制销毁,可能资源泄漏
最佳实践:确保 generator 析构前迭代完,或显式管理生命周期。
6.5 陷阱 5:std::mdspan 与内存对齐
double data[12];
auto mat = std::mdspan(data, 3, 4); // 默认行主序
// 跨平台:行主序 vs 列主序
auto mat_col = std::mdspan<double, std::dextents<size_t, 2>,
std::layout_left>(data, 3, 4); // 列主序
最佳实践:明确 LayoutPolicy,避免与 BLAS 库(默认列主序)冲突。
6.6 陷阱 6:std::print 不支持所有类型
struct Custom {
int x;
int y;
};
// std::print("{}", Custom{1, 2}); // 编译错误
// 解决方案:特化 formatter
template <>
struct std::formatter<Custom> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
auto format(const Custom& c, format_context& ctx) const {
return std::format_to(ctx.out(), "({}, {})", c.x, c.y);
}
};
std::print("{}", Custom{1, 2}); // OK
6.7 UB 清单
| UB 类型 | 描述 | 检测方法 |
|---|---|---|
std::expected 默认构造 | 部分版本不允许默认构造(无默认 T) | 编译期检查 |
std::flat_map 迭代器失效 | 插入/删除后迭代器失效 | ASan |
std::generator 越界访问 | 协程结束后继续 ++iter | UBSan |
std::mdspan 越界访问 | mdspan[i,j] 超出 extents | ASan |
| Deducing this 误用 | 在非成员上下文使用 this | 编译期错误 |
6.8 最佳实践清单
- 优先
std::expected:错误处理优于异常(在错误码风格代码中)。 std::flat_map用于小数据:大数据用std::map。- Deducing this 简化重载:避免 cv/引用类别的多重载。
std::generator替代手写迭代器:代码简洁,性能相当。std::print替代iostream:类型安全,性能更优。if consteval替代is_constant_evaluated:更清晰直观。std::mdspan用于多维数组:与 BLAS 集成无缝。
7. 工程实践
7.1 构建与依赖
cmake_minimum_required(VERSION 3.20)
project(cpp23_lib CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 检测编译器 C++23 支持
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-std=c++23" CXX23_SUPPORT)
if(NOT CXX23_SUPPORT)
message(FATAL_ERROR "C++23 support required")
endif()
# GCC 需要 -fexperimental-library 启用部分新库
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
add_compile_options(-fexperimental-library)
endif()
add_library(cpp23_lib STATIC src/lib.cpp)
target_compile_features(cpp23_lib PUBLIC cxx_std_23)
7.2 性能基准测试
// file: bench_cpp23.cpp
// compile: g++ -std=c++23 -O2 -fexperimental-library -o bench bench_cpp23.cpp -lbenchmark
#include <benchmark/benchmark.h>
#include <print>
#include <iostream>
#include <format>
static void BM_PrintCout(benchmark::State& state) {
for (auto _ : state) {
std::cout << "Hello " << 42 << " World\n";
}
}
BENCHMARK(BM_PrintCout);
static void BM_PrintStdPrint(benchmark::State& state) {
for (auto _ : state) {
std::print("Hello {} World\n", 42);
}
}
BENCHMARK(BM_PrintStdPrint);
static void BM_FormatString(benchmark::State& state) {
for (auto _ : state) {
auto s = std::format("Hello {} World", 42);
benchmark::DoNotOptimize(s);
}
}
BENCHMARK(BM_FormatString);
BENCHMARK_MAIN();
典型输出(GCC 14, x86-64):
-----------------------------
Benchmark Time CPU
-----------------------------
BM_PrintCout 510 ns 510 ns
BM_PrintStdPrint 120 ns 120 ns 快约 4 倍
BM_FormatString 85 ns 85 ns
7.3 调试技巧
1. 检查编译器版本:
g++ --version # 需要 14+ 才能完整支持 C++23
clang++ --version # 需要 17+
2. 启用实验性库(GCC):
g++ -std=c++23 -fexperimental-library file.cpp
3. MSVC 需要选项:
cl /std:c++latest /experimental:preprocessor file.cpp
4. 处理 <mdspan> 头文件:
- GCC 14+: 需
-fexperimental-library - Clang 17+: 直接支持
- MSVC 19.32+: 直接支持
7.4 跨编译器兼容性
// 检测特性支持
#if __has_include(<print>)
#include <print>
#define HAS_STD_PRINT 1
#else
#include <iostream>
#define HAS_STD_PRINT 0
#endif
#if __has_include(<expected>)
#include <expected>
#define HAS_STD_EXPECTED 1
#else
#include <tl/expected.hpp> // 第三方库回退
namespace std { using expected = tl::expected; }
#endif
void print_hello() {
#if HAS_STD_PRINT
std::print("Hello, World!\n");
#else
std::cout << "Hello, World!\n";
#endif
}
7.5 CI/CD 集成
# .github/workflows/cpp23-ci.yml
name: C++23 CI
on: [push, pull_request]
jobs:
gcc-14:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- run: |
sudo apt-get install -y g++-14 cmake ninja-build
cmake -B build -G Ninja \
-DCMAKE_CXX_COMPILER=g++-14 \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
cd build && ctest
clang-18:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 18
sudo apt-get install -y clang-18 cmake ninja-build
cmake -B build -G Ninja \
-DCMAKE_CXX_COMPILER=clang++-18 \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
cd build && ctest
msvc:
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
- uses: ilammy/msvc-dev-cmd@v1
with:
toolset: 14.4
- run: |
cmake -B build -G "Visual Studio 17 2022" `
-DCMAKE_SYSTEM_VERSION=10.0.22621.0
cmake --build build --config Release
cd build && ctest -C Release
8. 案例研究
8.1 案例一:Chromium base::expected
Chromium 在 C++23 标准化之前已经实现了 base::expected 模板,C++23 后逐步迁移至 std::expected:
// Chromium 风格
base::expected<int, base::Status> Parse(const std::string& s) {
// ...
return base::ok(value);
}
// C++23 风格
std::expected<int, Error> Parse(const std::string& s) {
// ...
return value;
}
8.2 案例二:Meta Folly 的 Expected
Folly 库的 folly::Expected 是 C++23 std::expected 的前身,提供了更丰富的 API。C++23 后部分代码可逐步迁移至标准库。
8.3 案例三:Qt 的 QString::contains
C++23 起 std::string::contains 模仿 Qt 5.0 的 QString::contains:
// Qt
QString s = "Hello World";
if (s.contains("World")) { /* ... */ }
// C++23
std::string s = "Hello World";
if (s.contains("World")) { /* ... */ } // C++23 起合法
8.4 案例四:HPC 中的 std::mdspan
std::mdspan 源自 Sandia National Laboratories 的 Kokkos 项目,已被 BLAS、LAPACK 等数值计算库采用:
// Kokkos 风格 → C++23 标准化
void axpy(double alpha,
std::mdspan<const double, std::dextents<size_t, 1>> x,
std::mdspan<double, std::dextents<size_t, 1>> y) {
for (size_t i = 0; i < x.extent(0); ++i) {
y[i] += alpha * x[i];
}
}
8.5 案例五:std::generator 简化迭代器实现
传统迭代器实现需要 100+ 行,std::generator 可压缩到 10 行:
// 传统方式:实现 input_iterator
class FibonacciIter {
int a_ = 0, b_ = 1;
public:
int operator*() const { return a_; }
FibonacciIter& operator++() { int c = a_ + b_; a_ = b_; b_ = c; return *this; }
bool operator==(const FibonacciIter&) const { return false; }
bool operator!=(const FibonacciIter& o) const { return !(*this == o); }
};
// C++23:std::generator
std::generator<int> fibonacci() {
int a = 0, b = 1;
while (true) { co_yield a; int c = a + b; a = b; b = c; }
}
代码量减少约 90%。
填空题知识点讲解
常见疑问 5:. C++23 引入了 std::expected<T, E>,对应 Rust 的 ______ 类型。
Result<T, E>。
常见疑问 6:. std::flat_map 的查找复杂度为 ______,插入复杂度为 ______。
;(最坏)。
常见疑问 7:. Deducing this 的关键字是 ______。
this(显式声明在第一个参数位置)。
常见疑问 8:. std::generator<T> 满足 ______ 概念,但不可 ______。
input_iterator;拷贝。
编程题知识点讲解
常见疑问 9:. 使用 std::expected 实现一个简单的 JSON 解析器(仅支持对象、字符串、数字)。
#include <expected>
#include <string>
#include <string_view>
#include <map>
#include <variant>
enum class ParseError { UnexpectedChar, UnexpectedEOF };
class JsonValue {
public:
using Object = std::map<std::string, JsonValue>;
using Value = std::variant<std::nullptr_t, bool, double, std::string, Object>;
private:
Value value_;
public:
JsonValue() : value_(nullptr) {}
JsonValue(double v) : value_(v) {}
JsonValue(std::string v) : value_(std::move(v)) {}
JsonValue(Object v) : value_(std::move(v)) {}
const Value& get() const { return value_; }
};
class JsonParser {
std::string_view src_;
size_t pos_ = 0;
char peek() const { return src_[pos_]; }
void next() { ++pos_; }
bool eof() const { return pos_ >= src_.size(); }
void skipWhitespace() {
while (!eof() && (peek() == ' ' || peek() == '\n' || peek() == '\t')) next();
}
std::expected<JsonValue, ParseError> parseValue() {
skipWhitespace();
if (eof()) return std::unexpected(ParseError::UnexpectedEOF);
char c = peek();
if (c == '"') return parseString();
if (c == '{') return parseObject();
if (c == '-' || std::isdigit(c)) return parseNumber();
return std::unexpected(ParseError::UnexpectedChar);
}
std::expected<JsonValue, ParseError> parseString() {
next(); // skip "
std::string s;
while (!eof() && peek() != '"') {
s += peek();
next();
}
if (eof()) return std::unexpected(ParseError::UnexpectedEOF);
next(); // skip "
return JsonValue(std::move(s));
}
std::expected<JsonValue, ParseError> parseNumber() {
std::string s;
while (!eof() && (std::isdigit(peek()) || peek() == '.' || peek() == '-')) {
s += peek();
next();
}
return JsonValue(std::stod(s));
}
std::expected<JsonValue, ParseError> parseObject() {
next(); // skip {
JsonValue::Object obj;
skipWhitespace();
if (!eof() && peek() == '}') { next(); return JsonValue(std::move(obj)); }
while (!eof()) {
skipWhitespace();
auto keyResult = parseString();
if (!keyResult) return std::unexpected(keyResult.error());
std::string key = std::get<std::string>(keyResult->get());
skipWhitespace();
if (eof() || peek() != ':') return std::unexpected(ParseError::UnexpectedChar);
next();
auto valResult = parseValue();
if (!valResult) return std::unexpected(valResult.error());
obj[std::move(key)] = std::move(*valResult);
skipWhitespace();
if (eof()) return std::unexpected(ParseError::UnexpectedEOF);
if (peek() == ',') { next(); continue; }
if (peek() == '}') { next(); return JsonValue(std::move(obj)); }
return std::unexpected(ParseError::UnexpectedChar);
}
return std::unexpected(ParseError::UnexpectedEOF);
}
public:
explicit JsonParser(std::string_view src) : src_(src) {}
std::expected<JsonValue, ParseError> parse() { return parseValue(); }
};
int main() {
JsonParser parser(R"({"name": "Alice", "age": 30})");
auto result = parser.parse();
if (result) {
std::println("Parsed successfully");
} else {
std::println("Parse error: {}", static_cast<int>(result.error()));
}
return 0;
}
常见疑问 10:. 使用 std::generator 实现一个无限素数生成器。
#include <generator>
#include <print>
#include <cmath>
bool isPrime(int n) {
if (n < 2) return false;
if (n < 4) return true;
if (n % 2 == 0) return false;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) return false;
}
return true;
}
std::generator<int> primes() {
int n = 2;
while (true) {
if (isPrime(n)) co_yield n;
++n;
}
}
int main() {
int count = 0;
for (int p : primes()) {
std::print("{} ", p);
if (++count >= 20) break;
}
std::print("\n");
// 输出:2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71
return 0;
}
常见疑问 11:. 使用 std::mdspan 实现矩阵转置。
#include <mdspan>
#include <vector>
#include <print>
void transpose(std::mdspan<const int, std::dextents<size_t, 2>> src,
std::mdspan<int, std::dextents<size_t, 2>> dst) {
for (size_t i = 0; i < src.extent(0); ++i)
for (size_t j = 0; j < src.extent(1); ++j)
dst[j, i] = src[i, j];
}
int main() {
std::vector<int> a = {1, 2, 3, 4, 5, 6};
std::vector<int> b(6);
auto A = std::mdspan(a.data(), 2, 3);
auto B = std::mdspan(b.data(), 3, 2);
transpose(A, B);
for (size_t i = 0; i < B.extent(0); ++i) {
for (size_t j = 0; j < B.extent(1); ++j) {
std::print("{} ", B[i, j]);
}
std::print("\n");
}
// 输出:1 4
// 2 5
// 3 6
return 0;
}
11.1 书籍
- 《C++23 — The Complete Guide》(Nicolai M. Josuttis, 2024):C++23 权威指南。
- 《Professional C++》(Marc Gregoire, 6th ed., 2024):覆盖 C++23 实战。
- 《Effective Modern C++》(Scott Meyers, 2014):C++11/14 基础,C++23 扩展。
- 《C++ Templates: The Complete Guide》(David Vandevoorde et al., 2nd ed., 2017):Deducing this 与模板深度结合。
11.2 论文与提案
- P2093: std::print formatting facility
- P0323: std::expected
- P0429: std::flat_map
- P0847: Deducing this
- P0009: std::mdspan
- P2502: std::generator
- P0288: std::move_only_function
- P1938: if consteval
- P1169: static operator()
- P1679: std::string::contains
- P2465: Standard Library Modules(import std)
11.4 视频课程
- CPPCon 2023: C++23: What’s New (Marc Gregoire) — C++23 概览。
- CPPCon 2023: Deducing this (Gašper Ažman) — Deducing this 深度讲解。
- Meeting C++ 2023: C++23 in Practice (Rainer Grimm) — 实战演示。
- MIT 6.S060: Programming Languages — 包含现代语言特性比较。
11.5 开源项目源码阅读
- range-v3: https://github.com/ericniebler/range-v3 — C++20 ranges 的前身,含 C++23 增强。
- tl::expected: https://github.com/TartanLlama/expected —
std::expected的前身。 - Kokkos mdspan: https://github.com/kokkos/mdspan —
std::mdspan的参考实现。 - Folly: https://github.com/facebook/folly — 大量 C++23 实践。
- GCC libstdc++: https://github.com/gcc-mirror/gcc/tree/master/libstdc++-v3 — 标准 C++ 库实现。
附录 A:C++23 特性速查表
| 特性 | 头文件 | 主要提案 |
|---|---|---|
std::print/println | <print> | P2093 |
std::expected | <expected> | P0323 |
std::flat_map/flat_set | <flat_map>/<flat_set> | P0429/P1222 |
std::mdspan | <mdspan> | P0009 |
std::generator | <generator> | P2502 |
std::move_only_function | <functional> | P0288 |
if consteval | (语言特性) | P2316 |
| Deducing this | (语言特性) | P0847 |
static operator() | (语言特性) | P0834 |
views::enumerate | <ranges> | P2164 |
views::zip | <ranges> | P2321 |
ranges::to | <ranges> | P1206 |
std::span 改进 | <span> | P2447 |
std::string::contains | <string> | P1679 |
std::unreachable | <utility> | P0627 |
多维 operator[] | (语言特性) | P2128 |
std::stacktrace | <stacktrace> | P0881 |
import std; 标准库模块 | (模块) | P2465 |
附录 B:编译器支持矩阵
| 编译器 | C++23 部分支持 | C++23 完整支持 |
|---|---|---|
| GCC | 12 | 14+ |
| Clang | 15 | 18+ |
| MSVC | 19.32 (VS 17.2) | 19.36 (VS 17.6) |
启用选项:
| 编译器 | 选项 |
|---|---|
| GCC | -std=c++23 -fexperimental-library |
| Clang | -std=c++23 -stdlib=libc++ |
| MSVC | /std:c++latest |
附录 C:与 C++20 关系
| C++20 特性 | C++23 增强 |
|---|---|
| concepts | 简化部分约束 |
| ranges | 新增 views::enumerate、views::zip、ranges::to 等 |
| coroutines | std::generator 标准化 |
| modules | 兼容性改进 |
<=> | 与 <format> 集成 |
C++23 是 C++20 的”完善版”,新增的库特性多于语言特性,更适合工程应用。
Deducing this
基本写法:显式对象参数
<返回类型> <方法名>(this <Self> <self>, <参数>);
// 一个方法同时支持 const 与非 const 调用
template <typename Self>
auto& get(this Self& self) {
return self.value;
}
基本写法:完美转发 self
auto&& <方法名>(this auto&& <self>) { return std::forward<decltype(<self>)>(<self>).<成员>; }
// 保留值类别以支持移动语义
template <typename Self>
auto&& data(this Self&& self) {
return std::forward<Self>(self).data_;
}
基本写法:CRTP 简化
void <方法名>(this auto&& <self>) { <self>.<实现>(); }
// 无需模板参数即可实现静态多态
struct Base {
template <typename Self>
void interface(this Self&& self) {
self.impl();
}
};
基本写法:递归 lambda
auto <名> = [](this auto <self>, <参数>) { <body> };
// lambda 自身递归调用
auto factorial = [](this auto self, int n) -> int {
return n <= 1 ? 1 : n * self(n - 1);
};
std::expected
基本写法:创建 expected
std::expected<<值类型>, <错误类型>> <变量>;
// 表示可能成功或失败的结果
std::expected<int, std::string> result = 42;
基本写法:返回失败
return std::unexpected(<错误>);
// 返回错误对象
return std::unexpected("parse error");
基本写法:检查是否有值
<result>.has_value() 或 static_cast<bool>(<result>)
// 判断 expected 是否持有成功值
if (result) { /* 使用 *result */ }
基本写法:获取值
*<result> 或 <result>.value()
// 取出成功值
int v = *result;
基本写法:获取错误
<result>.error()
// 取出错误对象
std::string err = result.error();
基本写法:单子转换 transform
<result>.transform(<函数>)
// 成功时对值应用函数
auto doubled = result.transform([](int x) { return x * 2; });
基本写法:失败处理 or_else
<result>.or_else(<函数>)
// 失败时应用函数
auto recovered = result.or_else([](std::string e) {
return std::expected<int, std::string>(0);
});
std::print
基本写法:格式化输出
std::print(<格式串>, <参数>...);
// 不换行的格式化输出
std::print("x = {}", x);
基本写法:换行输出
std::println(<格式串>, <参数>...);
// 自动换行的格式化输出
std::println("sum = {}", sum);
基本写法:输出到文件流
std::print(<文件流>, <格式串>, <参数>...);
// 输出到指定流
std::print(std::cerr, "error: {}", msg);
std::flat_map / std::flat_set
基本写法:创建 flat_map
std::flat_map<<键>, <值>> <变量>;
// 基于连续存储的有序映射
std::flat_map<int, std::string> m;
基本写法:插入元素
<map>.insert({<键>, <值>});
// 插入键值对
m.insert({1, "one"});
其他常用特性
基本写法:if consteval
if consteval { <编译时分支> } else { <运行时分支> }
// 判断是否在常量求值上下文
if consteval {
return compile_impl();
} else {
return runtime_impl();
}
基本写法:多维下标运算符
<返回类型> operator[](<索引1>, <索引2>);
// 支持多维索引
int& operator[](size_t i, size_t j);
基本写法:不可达声明
std::unreachable();
// 标记程序不应到达此处
throw std::logic_error("unreachable");
基本写法:枚举底层值
std::to_underlying(<枚举值>)
// 获取枚举的底层整数值
int n = std::to_underlying(Color::Red);
基本写法:字节序交换
std::byteswap(<整数>)
// 交换字节序用于端序转换
uint32_t be = std::byteswap(host_val);
基本写法:assume 属性
[[assume(<条件>)]];
// 给编译器提供假设以辅助优化
[[assume(n > 0)]];