跳至内容

C++23 与 C++26 预览

探索 C++23 的 std::expected、std::print、推导 this、范围改进,以及预览即将到来的 C++26 特性,如契约、反射和模式匹配。

持续演进

C++23 是一个「完善」版本,填补了 C++20 留下的空白并添加了呼声很高的特性。std::expected 带来了 Rust 风格的错误处理,std::print 提供了现代输出,推导 this 启用了强大的元编程模式,范围增加了数十个新视图。与此同时,C++26 正在发展为另一个重大版本,包含契约、反射和模式匹配 ── 这些特性将从根本上改变我们编写安全、表达力强的 C++ 代码的方式。

std::expected:现代错误处理

std::expected 持有类型 T 的成功值或类型 E 的错误。结合单子操作(.and_then()、.transform()、.or_else()),它实现了无需异常的可组合错误处理:

expected.cpp
#include <iostream>
#include <expected>
#include <string>
#include <charconv>
#include <system_error>

enum class ParseError {
    empty_input,
    invalid_format,
    out_of_range
};

std::expected<int, ParseError> parse_int(std::string_view sv) {
    if (sv.empty()) return std::unexpected(ParseError::empty_input);

    int result{};
    auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), result);

    if (ec == std::errc::invalid_argument)
        return std::unexpected(ParseError::invalid_format);
    if (ec == std::errc::result_out_of_range)
        return std::unexpected(ParseError::out_of_range);
    if (ptr != sv.data() + sv.size())
        return std::unexpected(ParseError::invalid_format);

    return result;
}

std::expected<int, ParseError> double_if_positive(int val) {
    if (val <= 0) return std::unexpected(ParseError::out_of_range);
    return val * 2;
}

int main() {
    // Direct usage
    auto result = parse_int("42");
    if (result) {
        std::cout << "Parsed: " << *result << "\n";
    } else {
        std::cout << "Error code: " << static_cast<int>(result.error()) << "\n";
    }

    // Monadic chaining — like Rust's Result::and_then
    auto chained = parse_int("21")
        .and_then(double_if_positive)       // int -> expected<int, E>
        .transform([](int v) { return v + 1; })  // int -> int (wrapped)
        .or_else([](ParseError e) -> std::expected<int, ParseError> {
            std::cout << "Recovering from error\n";
            return 0;  // default value on error
        });

    std::cout << "Chained result: " << *chained << "\n";  // 43

    return 0;
}

std::print 和 std::println 将 std::format 与输出结合,在大多数用例中替代 std::cout <<。它们更快、类型安全,并产生可读的代码:

print.cpp
#include <print>
#include <vector>
#include <string>
#include <ranges>

int main() {
    // Basic printing — no more << chains!
    std::println("Hello, {}!", "C++23");
    std::print("No newline here. ");
    std::println("But here.");

    // Formatting works just like std::format
    int x = 42;
    double pi = 3.14159;
    std::println("x = {}, pi = {:.2f}", x, pi);
    std::println("Hex: {:#x}, Binary: {:#b}", 255, 255);

    // Print to stderr
    std::println(stderr, "Warning: something happened");

    // Containers and ranges (C++23 formatter support)
    std::vector<int> v{1, 2, 3, 4, 5};
    std::println("Vector: {}", v);  // [1, 2, 3, 4, 5]

    // Formatted table
    std::println("{:<15} {:>10} {:>10}", "Name", "Score", "Grade");
    std::println("{:<15} {:>10} {:>10}", "Alice", 95, "A");
    std::println("{:<15} {:>10} {:>10}", "Bob", 87, "B+");

    return 0;
}

范围改进:zip、chunk、slide

C++23 添加了强大的新范围视图,覆盖了之前需要手动循环或第三方库的常见模式:

ranges_cpp23.cpp
#include <print>
#include <ranges>
#include <vector>
#include <string>

int main() {
    std::vector<std::string> names{"Alice", "Bob", "Carol"};
    std::vector<int> scores{95, 87, 92};

    // zip — combine multiple ranges element-wise
    for (auto [name, score] : std::views::zip(names, scores)) {
        std::println("{}: {}", name, score);
    }

    std::vector<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9};

    // chunk — split into groups of N
    for (auto chunk : data | std::views::chunk(3)) {
        std::print("[ ");
        for (int v : chunk) std::print("{} ", v);
        std::println("]");
    }
    // [ 1 2 3 ] [ 4 5 6 ] [ 7 8 9 ]

    // slide — sliding window of size N
    for (auto window : data | std::views::slide(3)) {
        std::print("( ");
        for (int v : window) std::print("{} ", v);
        std::println(")");
    }
    // ( 1 2 3 ) ( 2 3 4 ) ( 3 4 5 ) ... ( 7 8 9 )

    // cartesian_product — all combinations
    std::vector<char> suits{'H', 'D', 'C', 'S'};
    std::vector<int> ranks{1, 2, 3};
    for (auto [suit, rank] : std::views::cartesian_product(suits, ranks)) {
        std::print("{}{}  ", suit, rank);
    }
    std::println("");

    // enumerate — index + value (finally!)
    for (auto [i, name] : std::views::enumerate(names)) {
        std::println("[{}] {}", i, name);
    }

    return 0;
}

推导 this

C++23 的推导 this(也称为「显式对象参数」)允许成员函数将 this 作为显式的、可推导的参数。这消除了重复 const/非 const 重载的需要,启用递归 lambda,并简化 CRTP:

struct Widget {
    std::string name;

    // Before C++23: two identical overloads
    // const std::string& get_name() const { return name; }
    // std::string& get_name() { return name; }

    // C++23: one function handles both
    template<typename Self>
    auto&& get_name(this Self&& self) {
        return std::forward<Self>(self).name;
    }
};

参数 this Self&& self 推导对象是 const、非 const、左值还是右值 ── 并正确转发。这种模式称为「推导 this」惯用法,可以将成员函数重载集减少一半。

C++26 预览:契约、反射、模式匹配

C++26 预计将包含几个变革性特性:

契约 ── pre、post 和 contract_assert 允许前置条件、后置条件和断言,构建系统可以启用或禁用:

int sqrt_int(int x)
    pre(x >= 0)
    post(r: r * r <= x)
{
    // implementation
}

静态反射 ── 在编译时检查类型、成员和枚举值。基于结构体字段生成代码、自动推导序列化等 ── 全部无需宏。

模式匹配 ── inspect 表达式允许对类型、值和结构进行匹配:

inspect (variant_value) {
    <int> i    => std::println("int: {}", i);
    <string> s => std::println("string: {}", s);
    __         => std::println("other");
};

发送者/接收者(std::execution) ── 用于异步执行的标准框架,提供结构化并发,替代临时的线程池实现。

最佳实践

保持与 C++ 演进同步:

1. 为你的项目设定最低标准(C++17 是当今大多数生产代码的坚实基准)
2. 使用编译器标志选择标准:-std=c++17、-std=c++20、-std=c++23
3. 检查编译器支持 在 cppreference.com/compiler_support ── 并非所有编译器都实现了每个特性
4. 使用特性测试宏 如 __cpp_concepts、__cpp_lib_expected 来有条件地使用更新的特性
5. 阅读提案(wg21.link/pXXXX)了解你计划采用的特性
6. 不要追逐每个新特性 ── 采用能使你的代码库更简单、更正确的特性,而不仅仅是更新的

常见陷阱

过早采用最新标准特性会带来真实的成本:

编译器 bug:新特性通常有实现 bug,只有通过生产使用才能发现。例如,C++20 模块和协程在标准化后的 2-3 年内都有重大编译器 bug。

不完整的工具链:调试器、性能分析器和静态分析工具可能不完全支持最新特性。IDE 支持(IntelliSense、代码补全)往往也会滞后。

可移植性:如果你的代码必须在多个平台上编译(Linux、macOS、Windows、嵌入式),你将受限于所有目标编译器支持的交集。

团队准备度:新特性需要整个团队学习。只有一个开发者理解的特性会造成维护瓶颈。

关键要点
  • std::expected 启用 Rust 风格的错误处理,支持单子链式调用(.and_then()、.transform())
  • std::print / std::println 用简洁、快速、类型安全的格式化输出替代 std::cout <<
  • C++23 范围添加了 zip、chunk、slide、cartesian_product 和 enumerate 视图
  • 推导 this 消除了 const/非 const 成员函数重载的重复
  • C++26 目标包括契约、静态反射、模式匹配和用于异步的 std::execution
  • 渐进式采用新标准 ── 检查编译器支持、充分测试并确保团队准备就绪

测验 — 检验你的知识

(15 经验值)

1. `std::expected<int, Error>` 表示什么?

2. `std::views::zip(names, scores)` 产出什么?

3. 哪个 C++26 特性允许指定函数的前置条件和后置条件?