C++23 Core Language(核心语言特性)——详细理解

1. Explicit Object Parameters(显式对象形参)

这解决什么?

过去成员函数隐式拥有一个 this 指针,不能:

  • 指定参数传值或传引用
  • 做重载区分
  • 控制对象的 cvref 类型
    C++23 引入:
    你可以显式写出对象参数:
struct X {
    void f(this X& self, int a);  // self 就是 this,但显式写出来
};

好处

  • 可以重载:this X, this const X, this X&&
  • 允许制作更合理的 fluent API
  • 与函数式/管道式写法兼容

2. if consteval

用于判断当前是否处于编译期求值(constexpr 计算环境)。
对比:

  • consteval: 函数必须在编译期执行
  • constexpr: 可以编译期或运行期
  • if consteval: 允许写出“如果在编译期,则走编译期逻辑,否则走运行期逻辑”。
    示例:
constexpr int f(int x) {
    if consteval {
        return x * 2;    // 编译期逻辑
    } else {
        return x + 2;    // 运行期逻辑
    }
}

3. Multidimensional Subscript Operator(多维下标运算符)

C++23 允许自定义:

operator[](size_t i, size_t j)

可写成:

matrix[i, j];

生成更自然的多维数组 API(服务于 mdspan 等新库)。

4. Attributes on Lambda-Expressions(Lambda 上的属性)

可以写:

auto f = [][[nodiscard]]{ return 5; };

过去 Lambda 不能加属性。

5. Literal Suffix for size_t(为 size_t 增加字面量后缀)

新增后缀:uz
写法:

auto n = 123uz;   // type = size_t

方便避免写 size_t(123)

6. auto(x):语言级 decay-copy(衰减复制)

auto y = (x);    // 过去可能是引用折叠
auto z = auto(x) // C++23:始终 decay-copy

保证得到一个 值拷贝,不是引用。

7. #elifdef, #elifndef, #warning

更方便的预处理器分支:

#if DEBUG
#elifdef TESTING
#elifndef RELEASE
#endif

#warning:编译期发出警告。

8. Marking Unreachable Code(标记不可达代码)

关键字:[[unreachable]];

switch(x) {
    case 0: ...; break;
}
[[unreachable]];   // 告诉编译器这里不会发生

可使优化更积极。

9. Assumptions(假设)

[[assume(cond)]]。告知编译器条件永远为真。

[[assume(x > 0)]]
int y = 10 / x;

让优化器生成更快的代码(风险:程序员必须确保条件真的永远真)。

10. Named Universal Character Escapes(命名字符转义)

支持:

"\N{LATIN SMALL LETTER SHARP S}"

跨语言文字一致化。

11. Trim Whitespace Before Line Splicing(拼接行前裁剪空白)

改进了:

hello \
 world

这种行拼接在语义上的处理,变得更一致、更可控。

C++23 Standard Library(标准库)

1. String Formatting Improvements(格式化字符串改善)

std::format 性能更好,支持更多格式选项。
模块版本:std.format

2. Standard Library Modules(标准库模块)

C++23 初次完整支持模块化标准库:

  • import std;
  • import std.core;
  • import std.format;
    编译速度、可维护性更好。

3. std::flat_map / std::flat_set(平坦容器)

底层使用 排序 vector 而不是红黑树。
特点:

  • 内存连续,Cache 友好
  • 查找二分,速度快
  • 插入删除慢(因为 vector 移动)
    适合“读多写少”的场景。

4. std::mdspan(零开销多维视图)

mdspan = 多维数组的通用抽象。
无内存拥有权,只是视图。
用于:

  • HPC
  • 数值计算
  • 线性代数
  • 图像处理

5. std::generator(协程生成器)

使用协程实现:

std::generator<int> numbers() {
    for (int i=0;i<10;i++)
        co_yield i;
}

很像 python 的 yield

6. basic_string(_view)::contains()

终于可以写:

s.contains("abc");

7. From nullptr 构造 string

以前非法;现在允许:

std::string s = nullptr;   // 构造空串

8. resize_and_overwrite()

高性能地构造字符串:

s.resize_and_overwrite(n, [](char* p, size_t n) {
    // 写入 p,返回实际写入的大小
    return new_size;
});

避免临时 buffer,极快。

9. Optional 的 Monadic Operations

例如:

  • transform
  • and_then
  • or_else
    std::optional 更像函数式工具。

10. Stacktrace Library(堆栈跟踪)

std::stacktrace

std::cout << std::stacktrace::current();

调试大提升。

11. Ranges & Views 改进

  • 合并功能
  • 新增视图
  • 性能提升
  • 适配器可组合性更好

12. std::expected(标准化错误返回)

用于替代异常或 optional + error

expected<int, error> f();

变成标准,巨大提升错误处理表达能力。

13. std::move_only_function<>

对标 std::function(但只能移动)。
性能更好,不需要拷贝语义。

14. std::spanstream

基于 std::span 的 stream,不再使用动态分配。

15. std::byteswap()

统一的字节序转换:

auto x = std::byteswap(0x12345678u);

16. std::to_underlying()

得到枚举的底层整数值:

int x = std::to_underlying(Color::Red);

17. Heterogeneous Erasure(异类型擦除) for associative containers

例如:

std::map<std::string, int> m;
m.erase("abc"s);    // 现在支持异类型查找 + 删除

提高性能,避免构造临时 key。

Removed Features(移除特性)

1. Garbage Collection Support(移除了对 GC 的支持)

C++ 从未真正实现过 GC,但以前保留了 GC hooks 接口。
C++23 把这些接口彻底去掉。

C++23:Explicit Object Parameters(显式对象参数)详解

1. 背景:以前的 this 是隐式的

在 C++20/23 之前,成员函数都有一个隐式的 this 指针:

void setValue(double value) {
    m_value = value;   // 实际是 this->m_value
}

C++23 允许你显式写出 this 所绑定的对象参数

2. 显式对象参数(Explicit Object Parameter)语法

你可以这样写:

void setValue(this Cell& self, double value) {
    self.m_value = value;
}

效果

  • this 不再隐式出现
  • 第一个参数 明确表示“这个成员函数要绑定哪个对象”
    更类似于 Python 的 self 或 Rust 的 &self / &mut self / self

3. ref-qualified 成员函数的改写

有些类会写多个版本的同名函数,例如:

void f() &;           // 左值对象调用
void g() const &;     // 左值 const 对象调用
void h() &&;          // 右值对象调用

C++23 可以写成:

void f(this Data&);
void g(this const Data&);
void h(this Data&&);

优点

更统一、更清晰:

  • 左值:Data&
  • 常量左值:const Data&
  • 右值:Data&&

4. 大幅减少代码重复(重要!)

一个常见问题:同一个 getter 要写三份:

std::string& GetName() &;
const std::string& GetName() const &;
std::string&& GetName() &&;

使用 C++23:一份代码搞定!

template <typename Self>
auto&& GetName(this Self&& self) {
    return std::forward<Self>(self).m_name;
}

解析:

Self 推导出调用者类型:

  • 如果是左值非 const,Self = Person&
  • 如果是 const 左值,Self = const Person&
  • 如果是右值,Self = Person&&
    返回的语义完全正确(完美转发)。

5. 递归 lambda 终于变简单!

以前 lambda 的 this包围对象的 this,不是 lambda 自己的实例!
C++23:

auto fibonacci = [](this auto self, int n) {
    if (n < 2) return n;
    return self(n - 1) + self(n - 2);
};

这里的关键:

self 就是 lambda 自身,可以递归调用。
以前必须写 std::function 或用 Y Combinator 技巧,非常麻烦。

if consteval 深度解析(C++23 新语法)

1. 语法

if consteval {
    /* A */ 
} else {
    /* B */
}

没有条件表达式!
编译期会判断:此语句是否处在 常量求值环境(constant evaluation)中。

2. 行为解释

  • 如果当前语境是常量求值,则执行 A
  • 否则执行 B
    用数学描述:
    execute(A)  ⟺  during constant evaluation \text{execute}(A) \iff \text{during constant evaluation} execute(A)during constant evaluation

3. 为什么不用 std::is_constant_evaluated()

你可能认为写这个就行:

if (std::is_constant_evaluated()) {
    /*A*/
} else {
    /*B*/
}

if consteval 更好:

① 不需要包含头文件

因为它属于语言本身。

② A 块内部可以调用 consteval 函数

std::is_constant_evaluated() 的 if 块内 不一定被认为是立即函数上下文,因此有些 consteval 函数不能调用。

if consteval 不会被误写成 if constexpr

下面这种是错误的用法:

if constexpr (std::is_constant_evaluated()) {
    ...
}

因为 constexpr 会导致编译期决定,而不是运行期,让逻辑含义混乱。

4. 示例

consteval int f(int i) { return i; }
constexpr int g(int i) {
    if consteval {
        return f(i) + 1;   // 可以 —— 立即函数上下文
    } else {
        return 42;
    }
}

解释:

  • 在编译期执行时(如在 constexpr 上下文),返回 f(i) + 1
  • 运行期调用则返回 42

5. 对比 consteval 函数

consteval int h(int i) {
    return f(i) + 1;    // 必定在常量求值环境
}

h 必须 总是在编译期求值,而 g 是“不一定要编译期”。

小结

Explicit Object Parameters — 关键点

  • 允许你显式写出 this 参数
  • 可以减少大量代码重复
  • 统一 ref-qualified/const 成员函数
  • 支持递归 lambda
  • 更接近 Rust / Python 的对象方法语义

if consteval — 关键点

  • 新的语言层级语法
  • 用来区分编译期/运行期路径
  • 能调用 consteval 函数
  • std::is_constant_evaluated() 更安全、语义更明确

C++23:Multidimensional Subscript Operator(多维下标运算符)

1⃣ 背景:以前访问多维数据的方式

在 C++20 及以前,如果你想访问三维数组或多维矩阵,通常有两种选择:

选项 A:函数调用风格

data(x, y, z);
  • 优点:可以自定义访问逻辑
  • 缺点:语法上不像数组,下标访问不直观

选项 B:多级下标访问

data[x][y][z];
  • 优点:语法直观,类似数组
  • 缺点:如果底层不是严格嵌套数组,需要写很多 operator[] 或额外封装
  • 语义复杂:每个 operator[] 只能返回一维切片(slice),不能一次性访问多维

2⃣ C++23 新特性:多维下标运算符

C++23 允许你在单个下标操作中同时访问多个维度

data[x, y, z];
  • 核心点:在一个 operator[] 中传入多个索引
  • 语法更清晰,逻辑更直接

3⃣ 示例

假设你有一个三维矩阵类:

template<typename T>
class Matrix3D {
    T storage[100][100][100];
public:
    // C++23 多维下标运算符
    T& operator[](size_t x, size_t y, size_t z) noexcept {
        return storage[x][y][z];
    }
};

访问元素:

Matrix3D<int> mat;
mat[1, 2, 3] = 42;   // 单行表达式直接访问三维数据

4⃣ 数学表达式对应

如果把三维数组看作函数映射:
data:0..X−1×0..Y−1×0..Z−1→T data : {0..X-1} \times {0..Y-1} \times {0..Z-1} \to T data:0..X1×0..Y1×0..Z1T

  • 以前:data(x,y,z)data(x, y, z)data(x,y,z)data[x][y][z]data[x][y][z]data[x][y][z]
  • C++23:data[x,y,z]data[x, y, z]data[x,y,z]
    语法上更像函数 f(x,y,z)f(x, y, z)f(x,y,z),又保留数组风格。

5⃣ 优点总结

  1. 语法简洁:一次表达所有维度,不需要多级 operator[]
  2. 实现灵活:可以在单个函数里实现 bounds check、索引计算等逻辑
  3. 可读性好:直观表达访问多维数组的意图

C++23:Lambda 表达式属性

1⃣ 背景:以前(C++20 及之前)

Lambda 表达式生成一个函数对象(function object,也叫闭包对象)
在 C++20 及之前,你可以给这个函数对象本身添加属性,例如 [[deprecated]]

auto a = [] () [[deprecated]] { return 42; };
  • 这里的属性 [[deprecated]] 是附加到 整个闭包对象 上的
  • 但是无法单独控制 闭包对象的 operator() 函数 的属性

2⃣ C++23 的改进

C++23 允许你为 生成的函数对象的 call operator 添加属性,同时保留对闭包对象本身的属性:

auto a = [] [[nodiscard]] () [[deprecated]] { return 42; };

解析:

  1. [[nodiscard]] → 作用在 闭包对象本身
  2. [[deprecated]] → 作用在 call operator,即 operator()
  • 这样你可以同时控制两类属性:
    • 闭包对象属性:影响对象本身的行为
    • 函数调用属性:影响调用 a() 时的行为

3⃣ 可视化理解

作用对象 属性示例 说明
闭包对象 (lambda object) [[nodiscard]] 提醒不要丢弃返回值或对象
函数调用操作符 (operator()) [[deprecated]] 调用时提示已废弃

4⃣ 示例:结合使用

auto lambda = [] [[nodiscard]] () [[deprecated]] {
    return 42;
};
int x = lambda();  // 调用时编译器会警告:deprecated
  • [[nodiscard]] 提示:如果不使用 lambda 的返回值,编译器会警告
  • [[deprecated]] 提示:调用 lambda() 时编译器会警告

5⃣ 优势

  1. 更精细的属性控制
    • 以前只能标记函数对象
    • 现在可以单独标记 call operator
  2. 提升代码安全性和可维护性
    • [[nodiscard]] 避免丢弃重要返回值
    • [[deprecated]] 提醒过时调用

🟦 C++23:Literal Suffix for size_t

1⃣ 背景:以前的整数字面量后缀

在 C++20 及以前,整数字面量有以下后缀:

后缀 类型说明
U unsigned int
L long
UL unsigned long
LL long long
ULL unsigned long long
  • 问题:如果你想写 size_t 类型的字面量,通常需要手动转换:
auto n = static_cast<size_t>(10);

2⃣ C++23 新增后缀

后缀 说明
uzUZ 创建 std::size_t 类型字面量
zZ 创建与 std::size_t 对应的有符号整数类型(ssize_t 类似)

3⃣ 使用场景

假设有一个向量:

std::vector<int> data{11, 22, 33};

循环遍历时:

for (auto i = 0, count = data.size(); i < count; ++i) { /* ... */ }
  • 编译器可能报错:i 被推导为 int,而 countsize_t
  • 类型不匹配,尤其在 64 位平台上可能导致警告或错误

4⃣ 解决方案:使用 uz 字面量

for (auto i = 0uz, count = data.size(); i < count; ++i) {
    /* ... */
}
  • 0uz → 类型直接为 size_t
  • 避免类型不匹配的问题
  • 语法更简洁,读者可以直接看出这是 size_t 类型

5⃣ 数学与类型对应

如果用符号表示:

  • 以前循环中类型不匹配:
    i∈Z,count∈size_t,i<count i \in \mathbb{Z}, \quad count \in \text{size\_t}, \quad i < count iZ,countsize_t,i<count
  • 使用 uz 后:
    i∈size_t,count∈size_t,i<count i \in \text{size\_t}, \quad count \in \text{size\_t}, \quad i < count isize_t,countsize_t,i<count
    保证类型一致,消除隐式转换或警告。

优点

  1. 减少类型错误:不用手动 cast
  2. 代码可读性增强:看到 uz 就知道是 size_t
  3. 统一风格:循环、数组索引、大小参数等场景自然使用 size_t

C++23:auto(x) / auto{x}

1⃣ 背景:对象拷贝

在 C++20 及以前,拷贝一个对象通常写:

auto c = x;
  • 问题:
    • c 是一个 lvalue(左值),即使它是通过拷贝构造得到的
    • 在某些场景下,需要一个 prvalue(纯右值)来触发移动语义或传递给右值引用参数

2⃣ C++23 新特性

C++23 引入了:

auto(x)
auto{x}
  • 效果:产生一个 prvalue 拷贝
  • 意味着生成的值是右值,可以传递给右值引用函数,从而触发移动语义
  • 避免了因为左值导致的额外拷贝或语义错误

3⃣ 示例

假设有函数:

void process(int&& value) {
    std::println("{}", value);
}
void process_all(const std::vector<int>& data) {
    for (auto& i : data) {
        process(auto(i));   // C++23 decay-copy: i 变成 prvalue
    }
}
  • i 本身是 data 中元素的 左值引用
  • 直接传递给 process(int&&) 会报错(因为不能将 lvalue 绑定到 rvalue 引用)
  • 使用 auto(i) 会生成一个 右值拷贝,可以安全传递

4⃣ 类型和语义理解

  • 普通 auto
auto c = x;  // c 是 lvalue
  • C++23 auto(x)
auto c = auto(x); // c 是 prvalue
  • 数学视角
    x:lvalue→auto(x)x′:prvalue x : \text{lvalue} \quad \xrightarrow{auto(x)} \quad x' : \text{prvalue} x:lvalueauto(x) x:prvalue
  • 这保证了:
    • 移动语义可以被正确触发
    • 避免不必要的拷贝
    • 适合遍历容器或处理右值引用参数的场景

5⃣ 小结

  1. auto(x) / auto{x} → decay-copy,生成 prvalue
  2. 用于需要右值语义的场景
  3. 适合与 右值引用函数 搭配使用
  4. 避免了写 std::move(i) 的显式操作,使代码更自然

C++23:新预处理指令

1⃣ 背景:以前的预处理指令

在 C++20 及以前,我们常用条件编译指令:

指令 说明
#ifdef id 相当于 #if defined(id),如果宏 id 已定义,则执行下面的代码块
#ifndef id 相当于 #if !defined(id),如果宏 id 未定义,则执行下面的代码块
示例:
#ifdef DEBUG
    std::cout << "Debug mode\n";
#endif

2⃣ C++23 新增指令

指令 说明
#elifdef id 相当于 #elif defined(id),在 #if#elif 后使用,检查宏是否定义
#elifndef id 相当于 #elif !defined(id),在 #if#elif 后使用,检查宏是否未定义
#warning "message" 生成编译时警告,输出指定信息

3⃣ 使用示例

#if defined(VERSION1)
    // code for VERSION1
#elifdef VERSION2
    // code for VERSION2
#elifndef VERSION3
    // code if VERSION3 is NOT defined
#else
    #warning "No version defined, using default settings"
    // default code
#endif
  • #elifdef VERSION2 → 如果 VERSION2 被定义,则执行对应代码
  • #elifndef VERSION3 → 如果 VERSION3 没有被定义,则执行对应代码
  • #warning "..." → 编译器输出警告信息,但不会停止编译

4⃣ 使用场景

  1. 简化条件编译
    • 避免写长形式 #elif defined(id)
    • 代码更直观、易读
  2. 编译期提醒
    • 使用 #warning 提示开发者某些宏或设置未定义
    • 可作为临时代码或兼容性检查的工具

5⃣ 小结

  • #elifdef id = #elif defined(id)
  • #elifndef id = #elif !defined(id)
  • #warning "..." → 生成编译期警告信息
  • 优点:提高代码可读性,减少书写错误,便于维护条件编译逻辑

C++23:std::unreachable()

1⃣ 背景

在 C++ 中,有时我们知道某段代码在逻辑上永远不会被执行,例如:

  • switch 语句中枚举值已经覆盖了所有可能情况
  • 某些函数参数有严格限制
    以前,为了安全,编译器仍然可能生成检查代码,确保值落在合法范围内。这样会略微影响性能。

2⃣ 新特性

C++23 引入了:

#include <utility>
std::unreachable();
  • 功能:标记当前代码为 不可达(unreachable)
  • 效果:调用此函数会触发 未定义行为(UB)
  • 优化:编译器可因此省略安全检查,提高性能

3⃣ 示例

void do_something(int number_that_is_only_0_1_2_or_3) {
    switch (number_that_is_only_0_1_2_or_3) {
        case 0: case 2:
            handle_0_or_2();
            break;
        case 1:
            handle_1();
            break;
        case 3:
            handle_3();
            break;
        default:
            std::unreachable(); // 永远不会执行到这里
    }
}
  • 假设函数参数 只可能是 0、1、2、3
  • default 分支理论上永远不会执行
  • 使用 std::unreachable() 告诉编译器:这里不可达
  • 编译器可以省略检查,生成更高效代码

4⃣ 注意事项

  1. 未定义行为
    • 如果实际上执行了 std::unreachable(),程序行为是未定义的
    • 必须确保逻辑上确实不可达
  2. 优化潜力
    • 对于 switchif-else、枚举、模板等场景,使用 std::unreachable() 可以显著减少多余分支检查
  3. 数学理解
    假设参数空间 S=0,1,2,3S = {0,1,2,3}S=0,1,2,3,函数 f:S→行为f : S \to \text{行为}f:S行为,那么:
    ∀x∈S,f(x)有定义⇒default 分支不可达 \forall x \in S, f(x) \text{有定义} \quad \Rightarrow \quad \text{default 分支不可达} xS,f(x)有定义default 分支不可达
  • 此时调用 std::unreachable() 对应集合补集为空:
    Sc=∅⇒UB不会触发(在逻辑正确下) S^c = \emptyset \quad \Rightarrow \text{UB不会触发(在逻辑正确下)} Sc=UB不会触发(在逻辑正确下)

5⃣ 小结

  • std::unreachable():标记不可达代码
  • 优点:提升性能、帮助编译器优化
  • 风险:如果逻辑有误,触发未定义行为
  • 常用场景:switch 枚举、严格限制函数参数、模板元编程等

C++23:Assumptions(假设)

1⃣ 背景

在 C++20 及之前,各大编译器提供了**假设(assume)**的功能,但方式不统一:

编译器 假设语法
MSVC / ICC __assume(expr)
Clang __builtin_assume(expr)
GCC 通过 if (expr) {} else { __builtin_unreachable(); } 模拟
示例(GCC 风格):
int divide_by_32(int x) {
    if (x >= 0) {} 
    else { __builtin_unreachable(); } // 告诉编译器 x < 0 不可能
    return x / 32;
}

2⃣ C++23 标准化

C++23 引入标准化方式:

[[assume(expr)]];
  • 功能:告诉编译器 expr 必然为 true
  • 优势:不依赖编译器特性,统一语法
  • 编译器可据此优化生成更高效代码

3⃣ 示例

int divide_by_32(int x) {
    [[assume(x >= 0)]];  // 假设 x >= 0
    return x / 32;
}
  • 编译器可以在优化阶段忽略 x < 0 的情况
  • 提高性能,因为生成的除法操作可以假设输入非负,省略安全检查

4⃣ 数学理解

假设函数:
f(x)=x32,x∈Z f(x) = \frac{x}{32}, \quad x \in \mathbb{Z} f(x)=32x,xZ

  • 我们有假设:
    x≥0 x \ge 0 x0
  • 编译器可以基于这个假设优化生成的机器码,不再处理 x<0x < 0x<0 的情况
  • 换句话说,函数的有效域变为:
    Df=x∈Z∣x≥0 D_f = { x \in \mathbb{Z} \mid x \ge 0 } Df=xZx0
  • 对于 x∉Dfx \notin D_fx/Df,行为是未定义的(UB),必须保证假设成立

5⃣ 使用注意事项

  1. 性能导向[[assume(expr)]] 是用于性能优化,而非逻辑检查
  2. 未定义行为:如果 expr 实际为 false,程序行为未定义
  3. 适用场景
    • 已知输入约束的函数(如数组索引、非负整数等)
    • 循环迭代边界优化
    • 数学函数假设域优化

小结

  • [[assume(expr)]]:C++23 提供统一假设语法
  • 功能:帮助编译器优化,提升性能
  • 风险:违反假设会触发未定义行为
  • 类似概念:std::unreachable() 标记不可达代码

C++23:Named Universal Character Escapes

1⃣ 背景

在 C++20 及之前,表示 Unicode 字符通常使用 码点(code point)值

  • 单字符字面量:\uXXXX\UXXXXXXXX
  • 字符串字面量:\uXXXX\UXXXXXXXX
    问题
  • 可读性差,不容易理解 Unicode 字符的含义
  • 特别是在包含组合字符或不常用字符时

2⃣ C++23 新特性

C++23 允许使用 Unicode 名称 来代替码点值:

auto a { U'\N{LATIN CAPITAL LETTER A WITH MACRON}' };
auto b { u8"\N{LATIN CAPITAL LETTER A WITH MACRON}\N{COMBINING GRAVE ACCENT}" };
  • \N{...}:通过 Unicode 分配的名称指定字符
  • 适用于字符字面量、UTF-8/16/32 字符串字面量
  • 使代码更易读、可维护

3⃣ 对比示例

传统写法(C++20 及之前)

// UTF-32 单字符字面量 {LATIN CAPITAL LETTER A WITH MACRON}
auto a { U'\u0100' };
// UTF-8 字符串字面量 {LATIN CAPITAL LETTER A WITH MACRON}{COMBINING GRAVE ACCENT}
auto b { u8"\u0100\u0300" };
  • 问题:\u0100\u0300 对不熟悉 Unicode 的开发者来说几乎无法立即识别含义

C++23 写法

// UTF-32 单字符字面量
auto a { U'\N{LATIN CAPITAL LETTER A WITH MACRON}' };
// UTF-8 字符串字面量
auto b { u8"\N{LATIN CAPITAL LETTER A WITH MACRON}\N{COMBINING GRAVE ACCENT}" };
  • \N{...} 清楚表明每个字符的 Unicode 名称
  • 代码可读性显著提高
  • 特别适合文档化字符串、国际化软件、处理复杂 Unicode 数据时

4⃣ 数学/Unicode 表示理解

假设字符码点为 U+0100U+0100U+0100,组合重音码点为 U+0300U+0300U+0300

  • 传统方式表示:
    a=U′0˘100′,b=u8"0˘1000˘300" a = U'\u0100', \quad b = u8"\u0100\u0300" a=U0˘100,b=u8"0˘1000˘300"
  • C++23 方式表示:
    a=U′NLATIN CAPITAL LETTER A WITH MACRON′,b=u8"NLATIN CAPITAL LETTER A WITH MACRONNCOMBINING GRAVE ACCENT" a = U'\N{LATIN\ CAPITAL\ LETTER\ A\ WITH\ MACRON}', \quad b = u8"\N{LATIN\ CAPITAL\ LETTER\ A\ WITH\ MACRON}\N{COMBINING\ GRAVE\ ACCENT}" a=UNLATIN CAPITAL LETTER A WITH MACRON,b=u8"NLATIN CAPITAL LETTER A WITH MACRONNCOMBINING GRAVE ACCENT"
  • 逻辑上两种方式等价,但可读性大幅提升

5⃣ 使用场景

  1. 多语言文本处理
    • 字符名称明确,便于维护多语言字符串
  2. 国际化/本地化系统
    • 不需要查 Unicode 码点即可理解字符含义
  3. 文档化或教学用途
    • 适合演示 Unicode 字符及组合字符

小结

  • C++23 提供了 \N{UNICODE_NAME} 来表示 Unicode 字符
  • 优点:
    1. 提高代码可读性
    2. 减少对 Unicode 码点表的依赖
    3. 对组合字符和复杂符号支持良好

C++23:Trim Whitespace Before Line Splicing

1⃣ 背景

在 C++ 中,如果你在 代码行末使用反斜杠 \,表示 行继续(line continuation)

int i = 1 \
+ 42;
  • \ 告诉编译器,下一行是当前行的延续
  • 这个机制主要用于宏定义和长表达式分行

2⃣ 历史问题(C++20 及之前)

如果 \ 后面 有额外的空格或制表符

int i = 1  // 注意这里有空格
\ + 42;
  • 标准未定义行为
  • 不同编译器表现不一致:
    • GCC / Clang 输出:1
    • MSVC 输出:43

3⃣ C++23 新规定

C++23 规范化了空白处理

  • 规定: 在行拼接(line splicing)时,\ 后面的 所有空白都会被去掉
  • 这样保证不同编译器行为一致
    效果
int main()
{
    int i = 1 \
    + 42;
    std::cout << i;
}
  • 输出结果统一为:
    i=1 i = 1 i=1
  • 也就是说,反斜杠后面多余的空格被自动剔除,不再影响编译结果

4⃣ 总结

优点

  1. 消除历史兼容性问题
    • 在多编译器下行拼接行为一致
  2. 避免潜在错误
    • 无需担心意外空格破坏代码逻辑
  3. 增强可读性与可维护性
    • 开发者可以自由缩进续行而不用担心影响结果

小结

  • C++23 行继续规则

    \ 后的空白在行拼接前会被剔除

  • 确保跨平台行为一致,输出符合直观预期

C++23 字符串格式化改进

1⃣ 新增函数

C++23 引入了两个便捷的打印函数,基于 std::format()

函数 功能
std::print() 直接格式化并输出到标准输出(stdout)
std::println() 格式化输出并自动添加换行符

2⃣ 基本示例

std::string name { "CppCon" };
// 旧写法:cout + format
std::cout << std::format("Hello {} 2023!\n", name);
// C++23 写法:
std::print("Hello {} 2023!\n", name);   // 不自动换行
std::println("Hello {} 2023!", name);   // 自动换行
  • 以前需要写 std::cout << std::format(...)
  • C++23 可以直接用 print()println(),更简洁

3⃣ 容器范围(Ranges)格式化

C++23 支持 直接格式化 STL 容器

std::vector<std::pair<int, int>> v { {1, 2}, {3, 4} };
std::println("{}", v);   // 输出: [(1, 2), (3, 4)]
std::set<std::pair<int, int>> s { {1, 2}, {3, 4} };
std::println("{}", s);   // 输出: {(1, 2), (3, 4)}
std::map<int, int> m { {1, 2}, {3, 4} };
std::println("{}", m);   // 输出: {1: 2, 3: 4}
  • vector, set, map 等容器,std::println() 可以直接识别元素并格式化输出
  • 支持嵌套容器,如 vector<vector<int>>

4⃣ 范围格式化说明(Range Formatting Specifiers)

C++23 提供 范围格式化符,允许定制容器内元素的显示:

std::vector<std::string> strings{ "Hello"s, "World!\t2023"s };
// 默认输出:
std::println("{}", strings);        
// 输出: ["Hello", "World!\t2023"]
// 去掉引号并保持换行:
std::println("{::}", strings);      
// 输出: [Hello, World!    
// 2023]

嵌套容器示例

std::vector<std::vector<int>> vv { {11, 22}, {33, 44, 55} };
std::println("{}", vv);        // [[11, 22], [33, 44, 55]]
std::println("{:n}", vv);      // [11, 22], [33, 44, 55]
std::println("{:n:n}", vv);    // 按元素换行:
                                // 11, 22
                                // 33, 44, 55
std::println("{:n:n:*^4}", vv); // 自定义填充和宽度:
                                // *11*, *22*
                                // *33*, *44*, *55*
  • {}:默认格式
  • {:n}:每个嵌套容器换行显示
  • {:n:n}:嵌套元素换行
  • {:n:n:*^4}:自定义填充符、对齐方式、宽度

5⃣ 小结

C++23 字符串格式化改进带来的好处:

  1. 更简洁:不用再写 std::format + std::cout
  2. 直接支持容器输出,包括嵌套容器
  3. 灵活的范围格式化符,可以控制元素显示、对齐、填充
  4. 增强可读性,尤其是调试或日志输出时

C++23 标准库模块

C++23 引入了两个新的命名模块(named modules),用于更方便地导入标准库内容,减少传统 #include 的依赖。

1⃣ 模块列表

模块名称 功能说明
import std; 导入标准库中 std 命名空间 的所有内容,包括:
• C++ 头文件(如 std::sort()
• C 风格包装头文件(如 std::fopen()
import std.compat; 导入 std 模块内容 + 全局命名空间版本的 C 包装头文件(如 ::fopen()
  • std 模块相当于标准 #include 的集合
  • std.compat 提供对老旧 C 接口的全局命名空间兼容

2⃣ 使用示例:现代 “Hello World”

import std;  // 导入 std 模块
int main() {
    std::println("Hello World!");  // 直接使用 std 命名空间函数
}
  • 不再需要 #include <iostream>#include <format>
  • std::println 直接可用
  • 模块化导入编译速度更快,且避免宏污染

3⃣ 总结

  1. 模块化导入:通过 import 导入整个标准库模块
  2. 减少依赖:无需单独包含多个头文件
  3. 兼容性选择std.compat 提供对旧 C 接口全局命名空间的支持
  4. 现代用法:写一个 “Hello World” 只需一句 import std;

C++23 扁平关联容器(Flat Associative Containers)

C++23 提供了一类新的扁平(flat)关联容器,主要用于在顺序容器(如 vectordeque)之上实现类似 std::map / std::set 的接口。

1⃣ std::flat_mapstd::flat_multimap

  • 头文件#include <flat_map>
  • 特点
    1. 顺序容器 的适配器(adaptor)
    2. 提供 关联容器接口,类似 std::map
    3. 唯一键flat_map)或 重复键flat_multimap
    4. 键值按键排序
    5. 键和值分开存储,通常用 std::vectorstd::deque
  • 作用
    • 基于键快速检索值
    • 同时保留顺序容器的高缓存局部性(cache locality),适合大量小型键值对
  • 示例
#include <flat_map>
#include <string>
using namespace std::string_literals;
int main() {
    std::flat_map<int, std::string> myMap;
    myMap[2023] = "CppCon"s;
    // 自定义比较器和底层容器
    std::flat_map<int, std::string,
                  std::less<int>,            // 键排序方式
                  std::deque<int>,           // 键存储容器
                  std::deque<std::string>> myMap2;
}

2⃣ std::flat_setstd::flat_multiset

  • 头文件#include <flat_set>
  • 特点
    1. 类似 flat_map / flat_multimap
    2. 仅存储
    3. 不存储映射值(no mapped values)
  • 作用
    • 提供快速查找、排序的集合
    • 适合大量小键值集合场景

3⃣ 总结

容器 是否允许重复键 存储方式 典型用途
std::flat_map 键/值分开存储(vector/deque) 快速查找 + 迭代缓存友好
std::flat_multimap 同上 支持重复键的查找
std::flat_set 键存储 高效唯一键集合
std::flat_multiset 键存储 支持重复键集合
  • 优点
    1. 高缓存局部性,迭代效率高
    2. 键值分开存储,可自定义底层容器
    3. 接口类似传统关联容器,容易迁移

1⃣ “Flat” 的含义

  • Flat = 扁平化存储
    • 键(key)和值(value)直接存储在连续内存块里(通常用 std::vectorstd::deque
    • 不像 std::mapstd::set 那样使用 树结构(红黑树、平衡二叉树)
    • 所以访问元素时,缓存命中率高,迭代速度快
  • 对比
    | 容器类型 | 内部结构 | 访问特点 |
    | --------------- | ------------------ | ---------------------------- |
    | std::map | 红黑树 | 插入/删除对数复杂度,迭代顺序有序,但内存分散 |
    | std::flat_map | 连续数组(vector/deque) | 插入/删除需要移动元素,但迭代非常快,内存连续,缓存友好 |

2⃣ “Flat” 带来的好处

  1. 缓存局部性好(CPU cache friendly)
    • 因为数据在内存中是连续存储的
  2. 迭代性能高
    • for(auto& kv : myFlatMap) 遍历速度比树快
  3. 内存分配少
    • 比每个节点都分配内存的树更节省内存
      所以 flat_map / flat_set 的 flat 可以理解为:

“扁平存储的关联容器”,强调 内存连续、迭代高效,而不是树结构。

std::mdspan 理解

1⃣ 定义与用途
  • std::mdspan 定义在 <mdspan>
  • 多维数组视图(Multidimensional array view)
  • 可以看作是 C++20 的 std::span 的多维扩展
    • std::span 是对一段连续内存的非拥有性视图
    • mdspan 允许直接用多维索引访问数据
  • 支持 不同的内存布局策略(layout policies)
    • 例如行主序(row-major)、列主序(column-major)等
  • std::submdspan
    • mdspan 的子视图(slice)
    • 可以只看已有 mdspan 的一部分数据
2⃣ 示例分析
int* data { /* ... */ };
// 将 data 看作 2 行 2 列的二维数组
auto mySpan { std::mdspan(data, 2, 2) };
// 遍历并赋值
for (size_t i { 0 }; i < mySpan.extents().extent(0); ++i) {
    for (size_t j { 0 }; j < mySpan.extents().extent(1); ++j) {
        mySpan[i, j] = i * 1000 + j;
    }
}
  • data 是一块连续内存
  • std::mdspan(data, 2, 2) 将其视为 2×2 的二维数组
  • mySpan[i, j] 可以直接用 多维下标访问
  • mySpan.extents().extent(0) 获取第 0 维的长度(行数)
  • mySpan.extents().extent(1) 获取第 1 维的长度(列数)
3⃣ 特点与优势
  1. 非拥有性(Non-owning)
    • 类似 span,不管理内存
    • 可以快速创建对现有数组、vector、C 风格数组的多维视图
  2. 多维索引支持
    • mySpan[i, j, k] 可以访问任意维度的数据
  3. 灵活布局策略
    • 默认是 行主序(row-major)
    • 可指定列主序或自定义布局
  4. 高性能
    • 无额外内存分配
    • 可直接操作底层连续内存
      总结:
      std::mdspan 就像 一个多维的 span,提供 高性能、多维访问和灵活布局 的能力,非常适合科学计算、图像处理、矩阵运算等场景。

std::generator 理解

1⃣ 定义与用途
  • std::generator 定义在 <generator>
  • 标准协程生成器(coroutine generator)
  • 用于 按需生成序列数据,每次生成一个值给调用者,并在下次请求时继续执行
  • 相当于 C# 的 yield return 或 Python 的 yield
2⃣ 示例分析
std::generator<int> getSequenceGenerator(int startValue, int numberOfValues) {
    for (int i { startValue }; i < startValue + numberOfValues; ++i) {
        // 向调用者生成一个值,并挂起协程
        co_yield i;
    }
}
int main() {
    auto gen { getSequenceGenerator(10, 5) };
    for (const auto& value : gen) {
        std::print("{} (Press enter for next value)", value);
        std::cin.ignore();  // 用户按回车后获取下一个值
    }
}
  • co_yield i;
    • 生成一个值给调用者
    • 挂起协程,等待下一次迭代
  • auto gen { getSequenceGenerator(10, 5) };
    • 创建一个生成器对象 gen
    • 可以按需访问生成的值
  • for (const auto& value : gen)
    • 迭代生成器中的值
    • 每次迭代会 恢复协程执行,直到遇到下一个 co_yield
3⃣ 特点与优势
  1. 按需生成(Lazy Evaluation)
    • 值只在需要时生成
    • 节省内存,不必一次性创建整个序列
  2. 简化迭代逻辑
    • 不需要手动维护迭代器或索引
    • co_yield 自动挂起/恢复
  3. 协程支持
    • 可与 co_await 等协程机制配合
    • 支持异步生成数据
  4. 类型安全
    • std::generator<T> 明确生成类型为 T
      总结:
      std::generator 是 C++23 提供的 标准协程生成器,适合序列生成、流处理或按需计算场景。它将协程与迭代器结合,使代码更简洁、高效。

std::to_underlying() 理解

1⃣ 定义与用途
  • 定义在 <utility> 头文件中
  • 作用:将 枚举值 转换为其 底层类型(underlying type)
  • 在 C++23 之前,需要手动写:
static_cast<std::underlying_type_t<E>>(enum_value)
  • C++23 引入 std::to_underlying(),简化写法,更语义化
2⃣ 示例分析
enum class Color : uint32_t {
    Red   = 0xff0000,
    Green = 0x00ff00,
    Blue  = 0x0000ff
};
Color r { Color::Red };
// 传统方式
auto value1 { static_cast<std::underlying_type_t<Color>>(r) };
// C++23 简化方式
auto value2 { std::to_underlying(r) };
  • Color::Red 的底层类型为 uint32_t
  • std::to_underlying(r) 返回 0xff0000,类型为 uint32_t
  • 避免重复写模板语法,更易读
3⃣ 优势
  1. 简洁
    • 不再需要写长模板 static_cast<std::underlying_type_t<E>>(...)
  2. 安全
    • 明确意图:将枚举转换为底层类型
  3. 适用范围
    • 适用于 强类型枚举(enum class)
    • 对普通 enum 也适用,但主要解决 enum class 的类型安全问题
      总结:
      std::to_underlying() 是 C++23 提供的语义化工具函数,专门用于获取枚举值的底层数值,让代码更简洁、安全、易读。

std::move_only_function<> 理解(含对比说明)

1⃣ 传统 std::function<> 的限制
int Process(std::function<int()> f) {
    return f() * 2;
}
// 普通 lambda 可以调用
std::print("{}", Process([] { return 21; })); // 42
// 捕获 unique_ptr 的 lambda 会失败
std::cout << Process([p = std::make_unique<int>(42)] { return *p; });
  • 原因
    std::function 内部需要拷贝 lambda 对象
  • 如果 lambda 捕获了 不可拷贝的对象(如 std::unique_ptr
    • 拷贝构造函数被删除
    • 编译器报错 "Attempting to reference a deleted function"
2⃣ C++23 的 std::move_only_function<> 解决方案
int Process(std::move_only_function<int()> f) {
    return f() * 2;
}
// 普通 lambda
std::print("{}", Process([] { return 21; })); // 42
// 捕获 unique_ptr 的 lambda 也可以
std::print("{}", Process([p = std::make_unique<int>(42)] { return *p; })); // 84
  • 原理std::move_only_function<> 仅移动 lambda
  • 内部不需要拷贝,所以允许捕获不可拷贝对象
  • 保留 std::function 的 type-erasure 特性,同时扩展了适用场景
3⃣ 对比总结
特性 std::function<> std::move_only_function<>
Lambda 捕获可拷贝对象 支持 支持
Lambda 捕获不可拷贝对象 不支持 支持
是否可拷贝 (仅可移动)
内存开销 较大(拷贝 + type-erasure) 较小(移动 + type-erasure)
总结
  • std::move_only_function<> 是 C++23 为解决 捕获不可拷贝资源的 lambda 调用而引入的
  • 它允许在函数参数中使用 只移动的 lambda,大大增强了灵活性和性能

std::expected 理解

1⃣ 基本概念
  • 定义在 <expected>
  • 类型:std::expected<T, E>
    • T:期望值类型(expected value type)
    • E:错误类型(error type)
  • 保证 永远不为空,要么有值,要么有错误
  • 创建错误值std::unexpected()
2⃣ 示例
#include <expected>
#include <string>
#include <iostream>
int main() {
    // 包含正常值
    std::expected<int, std::string> a { 21 };
    // 包含错误值
    std::expected<int, std::string> b { std::unexpected("Some error"s) };
    // 判断是否有值
    if (a.has_value()) {
        std::cout << "a 的值: " << a.value() << "\n";
    } else {
        std::cout << "a 的错误: " << a.error() << "\n";
    }
    if (b.has_value()) {
        std::cout << "b 的值: " << b.value() << "\n";
    } else {
        std::cout << "b 的错误: " << b.error() << "\n";
    }
}

输出:

a 的值: 21
b 的错误: Some error
3⃣ 成员函数
成员函数 功能说明
has_value() 返回 true 表示包含期望值,false 表示包含错误值
value() 返回期望值引用,如果没有值则抛出 bad_expected_access
error() 返回错误值引用
4⃣ 使用场景
  • 替代传统的 返回错误码 + 输出参数 的方式
  • 避免抛异常开销,但依然提供安全的错误处理机制
  • 适合 函数可能失败 的场景,例如文件操作、解析、网络请求等
    总结
  • std::expected<T, E> 是 C++23 新增的类型安全错误处理工具
  • 它明确区分了 正常值错误值,可读性和安全性都更高
  • std::optional<T> 相比,额外提供了 错误信息

C++23 Views 库更新 理解

C++23 对 Ranges/Views 库做了大量扩展,主要是增加了一些方便处理序列和组合序列的视图(view)。下面详细解释各个新特性。

1⃣ views::zipviews::zip_transform
  • 功能:把多个 view 的第 n 个元素组合成一个 tuple。
  • zip:直接组合
  • zip_transform:组合的同时应用一个操作(如加、乘等)
std::vector v1 {1, 2};
std::vector v2 {'a', 'b', 'c'};
std::vector v3 {3, 4, 5};
auto r1 = std::views::zip(v1, v2);                 
// {(1,'a'),(2,'b')}
auto r2 = std::views::zip_transform(std::multiplies(), v1, v3);  
// {3, 8}   // 1*3=3, 2*4=8
2⃣ views::adjacentviews::adjacent_transform
  • adjacent:每个元素是原序列中连续 N 个元素的 tuple
  • adjacent_transform:每个元素是对连续 N 个元素应用某操作的结果
std::vector v {1,2,3,4};
auto r3 = v | std::views::adjacent<2>; 
// {(1,2),(2,3),(3,4)}
auto r4 = v | std::views::adjacent_transform<2>(std::multiplies()); 
// {2,6,12}  // 1*2=2, 2*3=6, 3*4=12
  • 别名
    • views::pairwise = views::adjacent<2>
    • views::pairwise_transform = views::adjacent_transform<2>
auto r5 = v | std::views::pairwise;
auto r6 = v | std::views::pairwise_transform(std::plus()); // 相邻元素求和
3⃣ views::slide
  • 功能:滑动窗口,类似 adjacent,但窗口大小在运行时指定
std::vector v {1,2,3,4,5};
auto r7 = v | std::views::slide(2);  
// {(1,2),(2,3),(3,4),(4,5)}
4⃣ views::chunkviews::chunk_by
  • chunk(N):按固定大小 N 分块
auto r8 = v | std::views::chunk(2); 
// {(1,2),(3,4),(5)}
  • chunk_by(predicate):按满足条件的块分组
std::vector v {1,2,2,3,0,4,5,2};
auto r9 = v | std::views::chunk_by(std::ranges::less_equal{});
// {(1,2,2,3),(0,4,5),(2)}
5⃣ views::join_with
  • 功能:将序列元素用指定分隔符连接
std::vector<std::string> v {"Hello","World","!"};
for(auto c: v | std::views::join_with('\n')) std::print("{}", c);
// 输出:
// Hello
// World
// !
6⃣ views::stride
  • 功能:选取等间距元素
std::vector v {1,2,3,4,5};
auto r10 = v | std::views::stride(2); // {1,3,5}
7⃣ views::repeat
  • 功能:重复元素
auto r11 = std::views::repeat(2,3); // {2,2,2} 重复3次
auto r12 = std::views::repeat(2);   // {2,2,2,...} 无限重复
8⃣ views::cartesian_product
  • 功能:生成多个 range 的笛卡尔积
std::vector v {0,1,2};
for(auto&& [a,b] : std::views::cartesian_product(v,v))
    std::println("{} {}", a,b);
// 输出:
// 0 0
// 0 1
// 0 2
// 1 0
// 1 1
// 1 2
// ...
9⃣ views::as_rvalue
  • 功能:将序列元素视为 rvalue,用于移动操作
std::vector<std::string> words {"Hello","World","2023"};
std::vector<std::string> movedWords;
std::ranges::copy(words | std::views::as_rvalue, std::back_inserter(movedWords));
// words 的元素被移动到 movedWords

总结

  • 这些新视图使 对序列进行组合、分块、滑动窗口、笛卡尔积、重复、步长等操作 更加简洁直观
  • 结合 Ranges + Views 可以写出类似 Python/Pandas 风格的链式操作
  • 适合处理 大数据序列、算法流水线和懒计算

C++23 Ranges 库更新 理解

C++23 对 Ranges 库(不仅是 Views,还包括对 range 的操作算法)增加了许多新功能,让序列处理更方便、直观。下面详细解释各个新特性。

1⃣ ranges::starts_with / ranges::ends_with
  • 功能:检查一个 range 的开始或结尾是否匹配另一个 range
  • 示例
std::vector v1{11, 22, 33, 44};
std::vector v2{11, 22};
std::println("{}", std::ranges::starts_with(v1, v2)); // true
std::println("{}", std::ranges::ends_with(v1, v2));   // false
  • 理解:类似字符串的 startswith / endswith,现在可以对任意 range 使用。
2⃣ ranges::shift_left / ranges::shift_right
  • 功能:对 range 元素整体左移或右移
  • 示例
std::vector<std::string> v{"a","b","c","d","e"};
std::ranges::shift_left(v, 2); 
// {"c","d","e","",""}
std::ranges::shift_right(v, 1); 
// {"","c","d","e",""}
  • 理解:左移/右移会在空缺位置填充默认构造值(如空字符串)。
3⃣ ranges::to()
  • 功能:将一个 range 转换为某种容器
  • 示例
auto ints = std::views::iota(1, 5) 
            | std::views::transform([](auto v){ return v*2; });
auto vec = std::ranges::to<std::vector>(ints);
std::print("{}", vec); // [2,4,6,8]
  • 可选用法
    • 转容器类型不同:
std::vector vec{33,11,22};
auto set1 = std::ranges::to<std::set>(vec);   // set<int>
auto set2 = vec | std::ranges::to<std::set<double>>(); // set<double>
std::set<double> set3{std::from_range, vec}; // 使用 from_range 构造
  • 结合 Views 使用
std::string text{"Lorem ipsum dolor sit amet"};
auto words = text
    | std::views::split(' ')
    | std::views::transform([](auto&& v){ return std::string{std::from_range, v}; })
    | std::ranges::to<std::vector>();
// ["Lorem","ipsum","dolor","sit","amet"]
4⃣ 查找与判断
  • 查找最后匹配元素
    • ranges::find_last(range, value)
    • ranges::find_last_if(range, predicate)
    • ranges::find_last_if_not(range, predicate)
    • 返回从找到元素到末尾的子 range,如果未找到返回空 range {last,last}
  • 判断包含
    • ranges::contains(range, value) → true/false
    • ranges::contains_subrange(range, subrange) → true/false
std::vector v1{11,22,33,44};
std::vector v2{33,44};
std::println("{}", std::ranges::contains(v1, 22));        // true
std::println("{}", std::ranges::contains_subrange(v1, v2));// true
5⃣ Folding 算法
  • 功能:折叠(累积)range 元素
  • 新算法
    • fold_left(), fold_left_first()
    • fold_right(), fold_right_last()
    • fold_left_with_iter(), fold_left_first_with_iter()
  • 示例
std::vector v{11,22,33,44};
std::println("{}", std::ranges::fold_left(v, 0, std::plus())); // 110
// 等价于 (((0+11)+22)+33)+44
  • 理解
    • fold_left:从左到右累加
    • fold_right:从右到左累加
    • 支持自定义初始值和操作符
    • 类似函数式编程中的 reduce / fold
      总结
  • C++23 Ranges 库强化了序列处理:
    • starts_with/ends_with:检查开头/结尾
    • shift_left/right:移动元素
    • to():range → 容器
    • contains / contains_subrange:判断是否包含
    • fold 系列:高阶折叠算法
  • 可以结合 Views链式懒计算,极大提高代码简洁性和可读性。

C++23 Stacktrace 库 理解

C++23 新增了 Stacktrace 库<stacktrace>),用于获取和处理程序运行时的调用栈信息。它可以帮助调试、日志记录以及异常处理。

1⃣ 获取当前调用栈
  • 函数std::stacktrace::current()
  • 返回值std::stacktrace 对象,包含当前线程的调用栈帧信息。
auto trace = std::stacktrace::current();
std::println("{}", trace);
  • VC++ 输出示例
0> D:\test\ConsoleApplication2.cpp(14): ConsoleApplication2!main+0x63
1> D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(79): ConsoleApplication2!invoke_main+0x39
2> ...
  • 理解:每一行显示一个调用帧(函数 + 偏移量 + 文件位置)。
2⃣ 异常中自动包含 Stacktrace
  • 用法:在异常对象中存储 std::stacktrace,方便捕获时打印。
class MyException : public std::exception {
public:
    MyException(std::string message, std::stacktrace st = std::stacktrace::current())
        : m_message{std::move(message)}, m_stacktrace{std::move(st)} {}
    const char* what() const noexcept override { return m_message.c_str(); }
    const std::stacktrace& trace() const noexcept { return m_stacktrace; }
private:
    std::string m_message;
    std::stacktrace m_stacktrace;
};
void bar() { throw MyException{"Some exception..."}; }
void foo() { bar(); }
int main() {
    try { foo(); }
    catch (const MyException& e) {
        std::println("Exception caught: {}", e.what());
        std::println("Stacktrace:\n{}", e.trace());
    }
}
  • 效果
    • 异常信息 + 调用栈输出
    • 可以快速定位问题发生的位置
3⃣ 遍历 stacktrace 的帧
  • 用法:通过范围 for 遍历每个 frame
auto trace = std::stacktrace::current();
for (auto&& frame : trace) {
    std::print("{}", frame.description());
}
  • VC++ 输出示例
ConsoleApplication2!main+0x63
ConsoleApplication2!invoke_main+0x39
ConsoleApplication2!__scrt_common_main_seh+0x12E
ConsoleApplication2!__scrt_common_main+0xE
ConsoleApplication2!mainCRTStartup+0xE
KERNEL32!BaseThreadInitThunk+0x14
ntdll!RtlUserThreadStart+0x21
  • 理解
    • frame.description() 返回每个调用帧的详细描述(函数名 + 偏移 + 模块信息)
    • 可以结合日志系统或调试工具使用
4⃣ 使用场景
  1. 调试:快速查看异常发生时的调用路径
  2. 日志记录:捕获错误堆栈信息,方便远程问题定位
  3. 性能分析:在分析函数调用链时,可以动态生成调用栈信息
    总结
  • <stacktrace> 是 C++23 的标准库特性,不再依赖平台特定 API
  • std::stacktrace 是不可修改的快照,记录调用栈帧
  • 可以与异常结合,自动记录堆栈信息
  • 提供迭代器接口,可逐帧访问或打印

C++23 basic_string::resize_and_overwrite() 详细理解(示例强化版)

resize_and_overwrite 是 C++23 为 std::basic_string 新增的性能优化函数,它允许在调整字符串大小的同时直接操作底层缓冲区,避免多次重复开销。下面详细解析你的示例。

1⃣ 函数行为回顾

std::size_t resize_and_overwrite(std::size_t count, auto op)
  • 功能
    • 将字符串大小调整为 count
    • 调用操作函数 op(char* data, size_t count) 写入内容
    • 根据操作函数返回的实际长度 r 裁剪多余部分
  • 逻辑
    1. count <= size() → 删除尾部 size() - count 个字符
    2. count > size() → 扩展尾部,增加 (count - size()) 个默认字符(通常为 '\0'
    3. 调用 r = op(data(), count) 写入内容
    4. 删除多余部分:erase(begin() + r, end())

2⃣ 常规示例(低效方式)

std::string GeneratePattern(const std::string& pattern, size_t count) {
    std::string result;
    result.reserve(pattern.size() * count);
    for (size_t i = 0; i < count; i++) {
        // Not optimal:
        // - Writes 'count' nulls
        // - Updates size and checks for potential resize 'count' times
        result.append(pattern);
    }
    return result;
}
  • 问题
    1. 每次 append(pattern) 都会修改 size() 并检查容量
    2. 如果 count 很大,会多次重复写入、调整 size(),效率低
    3. 虽然 reserve() 避免了多次 realloc,但仍然存在多次尾部初始化开销

3⃣ 使用 resize_and_overwrite 的高效实现

std::string GeneratePattern(const std::string& pattern, size_t count) {
    std::string result;
    const auto step = pattern.size();
    // GOOD: No initialization, single allocation
    result.resize_and_overwrite(step * count, [&](char* buf, size_t n) {
        for (size_t i = 0; i < count; i++) {
            // GOOD: 直接在缓冲区写入,不涉及 size() 更新
            memcpy(buf + i * step, pattern.data(), step);
        }
        return step * count; // 实际写入长度
    });
    return result;
}

分析:

  1. 预分配一次空间
    • resize_and_overwrite(step * count, ...) 直接在底层缓冲区扩展到目标大小,无需逐次追加
  2. 直接写入
    • memcpy(buf + i*step, pattern.data(), step) 将每次模式直接拷贝到缓冲区对应位置
    • 避免了 append 的额外 size() 更新和边界检查
  3. 返回实际写入长度
    • return step * count;
    • 字符串尾部多余空间(如果有)会被 erase 裁剪掉
  4. 效果
    • 性能提升明显
    • 内存操作更集中,减少重复初始化

4⃣ 总结

  • resize_and_overwrite = “一次性分配 + 直接写入 + 根据返回值裁剪”
  • 适合高性能字符串构造场景,例如:
    • 拼接大量固定模式
    • 序列化/反序列化
    • 大量二进制数据处理
      核心对比

方法 操作次数 性能 内存开销
append 循环 多次写入 & size() 更新 较高
resize_and_overwrite 单次分配 & 写入 较低

C++23 std::optional 的单子(Monadic)操作 详细理解

C++23 为 std::optional 新增了 monadic-style 的成员函数,使得对可选值的操作可以链式调用,而无需每次都判断是否为空。下面详细解析你的示例。

1⃣ 新增成员函数

假设有 std::optional<T> opt

函数 功能
transform(F) 如果 opt 有值,返回 optional<F(*opt)>;否则返回空 optional
and_then(F) 如果 opt 有值,返回 F(*opt)F 必须返回 optional<U>;否则返回空 optional
or_else(F) 如果 opt 有值,返回 *this;否则返回 F() 的结果(必须是 optional<T>

这些函数体现了 单子(monad)风格,类似 Haskell 或 Rust 的 Option<T>

2⃣ 示例解析

std::optional<int> Parse(const std::string& s) {
    try { return std::stoi(s); }   // 尝试将字符串转为整数
    catch (...) { return {}; }      // 转换失败返回空 optional
}
int main() {
    while (true) {
        std::string s;
        std::getline(std::cin, s);
        auto result = Parse(s)
            .and_then([](int value) -> std::optional<int> { 
                return value * 2; 
            })
            .transform([](int value) { 
                return std::to_string(value); 
            })
            .or_else([] { 
                return std::optional<std::string>{"No Integer"}; 
            });
        std::println("{}", *result);
    }
}

3⃣ 操作链逐步解释

假设输入 "12"

  1. Parse("12")optional<int>{12}
  2. .and_then(...)
    • 调用 lambda:12 * 2 = 24
    • 返回 optional<int>{24}
  3. .transform(...)
    • 调用 lambda:std::to_string(24)"24"
    • 返回 optional<std::string>{"24"}
  4. .or_else(...)
    • opt 有值 → 返回自身 "24"
      输出:
24

输入 "ee"(非数字):

  1. Parse("ee") → 空 optional<int>{}
  2. .and_then(...) → 空,直接跳过 lambda
  3. .transform(...) → 空,直接跳过 lambda
  4. .or_else(...) → 调用 lambda 返回 "No Integer"
    输出:
No Integer

4⃣ 总结

  • 链式操作
    • 可以把对 optional 的操作组合成管道,不需要每次检查 has_value()
  • 函数职责分离
    • transform → 转换值类型
    • and_then → 链接返回 optional 的操作
    • or_else → 提供默认值
  • 优势
    • 代码简洁,可读性高
    • 避免嵌套的 if(opt) 判断
    • 函数式风格,易于组合
      小技巧:
  • transformand_then 的差别:
    • transform(F) → F 返回 普通类型,结果会被包成 optional
    • and_then(F) → F 返回 optional 类型,适合链式传递可能为空的操作
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐