C++23
概述
C++23 包含以下新的语言特性:
- [常量求值 if 语句(consteval if)](#常量求值 if 语句(consteval if))
- [推导 this 指针(deducing
this)](#推导 this 指针(deducing this)) - 多维下标运算符
- [提升基于范围的 for 循环安全性](#提升基于范围的 for 循环安全性)
C++23 包含以下新的库特性:
- [堆栈跟踪库(stacktrace library)](#堆栈跟踪库(stacktrace library))
- [字符串和字符串视图的 contains 方法](#字符串和字符串视图的 contains 方法)
- std::to_underlying
spanstream- [输入/输出指针(input/output pointers)](#输入/输出指针(input/output pointers))
- [std::optional 的单子操作(monadic operations)](#std::optional 的单子操作(monadic operations))
std::expectedstd::unreachable
C++23 语言特性
常量求值 if 语句(consteval if)
编写可在常量求值期间实例化的代码。
c++
consteval int f(int i) { return i; }
constexpr int g(int i) {
if consteval {
return f(i);
} else {
return 42;
}
}
推导 this 指针(deducing this)
C++23 引入了显式对象成员函数,现在可以通过指定成员函数的第一个参数(以 this 关键字为前缀),推导对象的类型和值类别:
c++
// C++23 新写法(推导 this):
struct T {
decltype(auto) operator[](this auto& self, std::size_t idx) {
return self.mVector[idx];
}
};
// 旧写法:
struct T {
value_t& operator[](std::size_t idx) {
return mVector[idx];
}
const value_t& operator[](std::size_t idx) const {
return mVector[idx];
}
};
多维下标运算符
可为 operator[] 运算符指定零个或多个参数:
c++
template <typename T, std::size_t Z, std::size_t Y, std::size_t X>
struct Array3d {
std::array<T, X * Y * Z> m{};
T& operator[](std::size_t z, std::size_t y, std::size_t x) {
return m[z * Y * X + y * X + x];
}
};
Array3d<int, 4, 3, 2> v;
v[3, 2, 1] = 42;
提升基于范围的 for 循环安全性
修复了 C++ 中最重要的控制结构之一存在的部分臭名昭著的生命周期问题。
以下是 C++23 之前存在问题、现在已修复的代码片段示例:
for (auto e : getTmp().getRef())for (auto e : getVector()[0])for (auto valueElem : getMap()["key"])for (auto e : get<0>(getTuple()))for (auto e : getOptionalCollection().value())for (char c : get<std::string>(getVariant()))
C++23 库特性
堆栈跟踪库(stacktrace library)
堆栈跟踪是调用序列的近似表示,由多个堆栈跟踪条目组成。一个堆栈跟踪条目(由 std::stacktrace_entry 表示)包含的信息包括源文件和行号,以及一个描述字段。
Linux 系统上的示例输出:
c++
#include <print>
#include <stacktrace>
int main() {
std::println("{}", std::stacktrace::current());
}
0# main at /app/example.cpp:5 [0x5ee42e3db747]
1# <unknown> [0x76e76dc29d8f]
2# __libc_start_main [0x76e76dc29e3f]
3# _start [0x5ee42e3db644]
字符串和字符串视图的 contains 方法
一种更简洁的方法,用于查询字符串或字符串视图中是否包含某个子串:
c++
std::string{"foobarbaz"}.contains("bar"); // == true
std::string{"foobarbaz"}.contains("bat"); // == false
std::to_underlying
支持将枚举类型转换为其底层类型这一常用工具函数:
c++
enum class MyEnum : int { A = 1, B, C };
std::to_underlying(MyEnum::A); // == 1
std::to_underlying(MyEnum::C); // == 3
spanstream
strstream 的替代方案,使用字符跨度(span)作为外部提供的缓冲区。不拥有缓冲区所有权,也不会对缓冲区进行重新分配。
c++
char input[] = "10 20 30";
std::ispanstream is{std::span<char>{input}};
int i;
is >> i; // i == 10
is >> i; // i == 20
is >> i; // i == 30
c++
char output[30]{}; // 零初始化数组
std::ospanstream os{std::span<char>{output}};
os << 10 << 20 << 30;
std::span<char> sp = os.span();
输入/输出指针(input/output pointers)
std::out_ptr 和 std::inout_ptr 是一种抽象,通过创建临时的指向指针的指针(pointer-to-pointer)来同时支持 C API 和智能指针,该临时指针会在析构时更新智能指针。简而言之:它是一种可转换为 T** 的类型,当它离开作用域时,会更新(通过调用 reset 或语义等效的行为)创建它时所关联的智能指针。
该抽象还能在抛出异常时安全管理关联内存的生命周期。
c++
// p_handle 仅用于写入(输出)。
int c_api_create_handle(MyHandle** p_handle);
// p_handle 既用于读取(输入),也用于写入(输出)。
int c_api_recreate_handle(MyHandle** p_handle);
void c_api_delete_handle(MyHandle* handle);
struct resource_deleter {
void operator()(MyHandle* handle) {
c_api_delete_handle(handle);
}
};
c++
std::unique_ptr<MyHandle, resource_deleter> resource(nullptr);
int err = c_api_create_handle(std::out_ptr(resource));
// `resource` 现在拥有 `c_api_create_handle` 内部分配的内存。
c++
std::shared_ptr<MyHandle> resource(nullptr);
int err = c_api_recreate_handle(std::inout_ptr(resource), resource_deleter{});
// `resource` 现在共享 `c_api_recreate_handle` 内部分配的内存。
inout/out 指针均支持隐式转换为 void**,也支持显式转换为用户指定的类型。
std::optional 的单子操作(monadic operations)
为 std::optional 支持多种 and_then、transform 和 or_else 操作。
c++
std::optional<int> parse_int(const std::string&);
std::optional<int> ensure_non_negative(int);
std::optional<double> default_value_or_empty(double);
std::optional<double> stringToSqrtDouble(const std::string& input) {
return parse_int(input)
.and_then(ensure_non_negative)
.transform([](int x) {
return std::sqrt(x);
})
.or_else(default_value_or_empty);
}
std::expected
std::expected 提供了一种方式来表示一个值和一个潜在的错误值,两者都包含在一个类型中。还支持对预期值(expected)和非预期值(unexpected,即错误值)执行多种单子操作。
使用 std::unexpected 存储非预期值(即错误值)。
c++
enum class StringToSqrtDoubleError {
ParseError, NegativeNumber
};
std::expected<int, StringToSqrtDoubleError> parse_int(const std::string&);
std::expected<double, StringToSqrtDoubleError> stringToSqrtDouble(const std::string& input) {
auto parsed = parse_int(input);
if (!parsed) return parsed;
auto parsedInt = *parsed;
if (parsedInt < 0) return std::unexpected(StringToSqrtDoubleError::NegativeNumber);
return std::sqrt(parsedInt);
}
std::unreachable
提供一种显式标记代码路径为"不可达"的方式。如果该代码路径被执行到,可能会表现出未定义行为。
c++
enum class MyEnum { A, B, C };
int convertMyEnumToInt(MyEnum e) {
switch (e) {
case MyEnum::A: return 0;
case MyEnum::B: return 1;
case MyEnum::C: return 2;
default: std::unreachable();
}
}