六个月以来,国际C++标准化工作组已经召开了两次会议。
在第一次会议上,委员会集中于完善C++23的特性,其中包括:
在第二次会议上,委员会致力于开发C++26的新特性,包括:
去年夏天,委员会在C++23中添加了静态运算符(),并使为多个参数定义运算符[]成为可能。下一个合乎逻辑的步骤是给予这些操作符平等的机会,即添加写入静态操作符[]的能力。
enum class Color { red, green, blue };struct kEnumToStringViewBimap {static constexpr std::string_view operator[](Color color) noexcept {switch(color) {case Color::red: return "red";case Color::green: return "green";case Color::blue: return "blue";static constexpr Color operator[](std::string_view color) noexcept {if (color == "red") {return Color::red;} else if (color == "green") {return Color::green;} else if (color == "blue") {return Color::blue;assert(kEnumToStringViewBimap{}["red"] == Color::red);
这段代码真的能有效地将字符串转换为枚举吗?
这可能会让人吃惊,但代码实际上非常高效。编译器开发人员 使用类似的方法,我们在 用户服务器 框架。我们创建了一个名为 实用程序::平凡双向映射具有更方便的界面。
constexpr utils::TrivialBiMap kEnumToStringViewBimap = [](auto selector) {return selector().Case("red", Color::red).Case("green", Color::green).Case("blue", Color::blue);
由于现代优化编译器的特性,实现了高效率(但在编写通用解决方案时必须非常小心)。提案P2589R1描述了所有必要的详细信息。
constexpr函数中的静态constexpr
C ++23扩展了它的功能,增加了 常量表达式至字符/自字符。但是,一些实施者遇到了一个问题。一些标准库包含常量数组,用于快速转换 弦<>数,这些变量在函数中声明为静态变量。不幸的是,这妨碍了它们在 常数表达式功能。这个问题是可以解决的,但解决方法看起来真的很笨拙。
最终,委员会通过允许使用 静态常量表达式 变量在 常数表达式 函数,如中所述 P2647R1。一个小小的,但受欢迎的改进。
基于安全范围
这可能是最令人兴奋的消息出来的最后两次会议!
说到这里,我们先来猜个有趣的谜语:你能找出代码中的错误吗?
class SomeData {public:const std::vector& Get() const { return data_; }private:std::vector data_;SomeData Foo();int mAIn() {for (int v: Foo().Get()) {std::cout << v << ',';
答案就在这里。
基于范围的for循环涉及很多底层进程,因此,这些类型的bug可能并不总是显而易见的。虽然通过消毒剂测试可以有效地发现这些问题,但现代项目通常将其作为标准实践(在Yandex,我们也不例外)。然而,理想的做法是尽可能避免此类错误。
在RG21,我们在四年前首次尝试通过以下方式改善这种情况 D0890R0。遗憾的是,这一进程在讨论阶段停滞不前。
谢天谢地,Nicolai Josuttis掌握了主动权,在C ++23中,类似的代码将不再创建悬空引用。属性右侧创建的所有对象 :在基于范围的for循环中,现在仅在退出循环时才被破坏。
有关更多技术详情,请参阅文件P2718R0。
标准::打印
在C ++23中,有一个小的但值得注意的更新 标准::打印:其输出已被调整为与其它数据输出"同步"。虽然现代操作系统上的标准库不太可能经历任何明显的更改,但更新后的标准现在保证消息将按照它们在源代码中出现的顺序输出到控制台:
printf("first");std::print("second");
应为std::的一元接口
C ++23在最后一刻添加了一个相当重要的特性: 包含了一元接口,用于 std::预期 ,类似于已可用于的一元接口标准::可选。
using std::chrono::system_clock;std::expected from_iso_str(std::string_view time);std::expected to_bson(system_clock time);std::expected insert_into_db(formats::bson::Timestamp time);// Somewhere in the Application code...from_iso_str(input_data).and_then(&to_bson).and_then(&insert_into_db)// Throws “Exception” if any of the previous steps resulted in an error.transform_error([](std::string_view error) -> std::string_view {throw Exception(error);
您可以找到的所有一元接口的完整描述 std::预期 在 P2505R5。
static_assert(假)等
除了上面概述的重大变化外,还进行了大量的修订,以消除小的粗糙边缘并改进日常开发。例如,的格式化程序 标准::线程::ID 以及 标准::堆栈跟踪 (英文) 小行星2693 ),以便它们可以与 标准::打印 以及 标准::格式。标准::开始寿命为 还接收了额外的编译时签入 小行星2679 。值得注意的是, 静态断言(假)在模板函数中,不再在不实例化函数的情况下触发,这意味着只有在传递错误的数据类型时,才会编译如下代码并发出诊断:
templateint foo() {if constexpr (std::is_same_v) {return 42;} else if constexpr (std::is_same_v) {return 24;} else {static_assert(false, "T should be an int or a float");
除了前面提到的变化之外,C ++23中的范围还做了无数的改进。其中最重要的是包括 标准::视图::枚举 在 小行星2164:
#includeconstexpr std::string_view days[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",for(const auto & [index, value]: std::views::enumerate(days)) {print("{} {} n", index, value);
C++26语言聚合的std::get和std::tuple_size
有一个令人兴奋的新想法可以改进C++,我们已经在Yandex Go和用户服务器框架中积极使用了这个想法,多亏了Boost.PFR,任何想要使用它的人都可以使用它。
如果您正在编写一个通用模板库,那么您可能需要使用std::tuple和std::pair。但是,这些类型存在一些问题。首先,它们使代码难以阅读和理解,因为字段没有明确的名称,并且很难辨别std::get<0>(tuple)之类的内容的含义。此外,库的用户可能不想直接使用这些类型,而是在调用方法之前创建这些类型的对象,这可能会由于数据复制而导致效率低下。其次, std::tuple和std::pair不会“传播”它们存储的类型的平凡性。因此,当从函数传递和返回std::tuple和std::pair时,编译器可能生成效率较低的代码。
然而,聚集体--具有公共字段且没有特殊功能的结构--没有上述缺点。
背后的想法 P2141R0 是允许在泛型代码中使用聚合 标准::获取 以及 标准::元组大小和他们一起工作。这将使用户能够将他们的结构直接传递到您的泛型库,而无需进行不必要的复制。
这个想法得到了委员会的好评,我们将继续测试和解决任何潜在的粗糙边缘。
包埋数量
目前,正在积极开发一个新的C语言标准(无类的,没有++),它包括许多在C++中早已存在的有用特性(例如nullptr、auto、constexpr、static_assert、thread_local、[[noreturn]]),以及C++的全新特性。好消息是,一些新特性将从新的C标准移植到C++26。
#embed就是其中一个新特性--一个预处理器指令,用于在编译时将文件内容替换为数组:
const std::byte icon_display_data[] = {#embed "art.png"
一些次要的细节需要解决。有关该理念的完整描述,请参见 小行星1967。
从异常获取std::stacktrace
WG21的想法是 小行星2370遭遇了意想不到的挫折。
从异常获取堆栈跟踪的能力在大多数编程语言中都存在。此功能非常有用,允许进行信息量更大、更易于理解的诊断,而不是像“捕获异常”这样信息量更小的错误消息:地图::位置:
Caught exception: map::at, trace:0# get_data_from_config(std::string_view) at /home/axolm/basic.cpp:6001# bar(std::string_view) at /home/axolm/basic.cpp:62# main at /home/axolm/basic.cpp:17
当在持续集成(CI)环境中使用时,这个特性非常有用。它允许您快速识别测试中的问题,并避免在本地重现问题的麻烦,这可能并不总是可能的。
不幸的是,国际委员会并没有完全接受这个想法。我们将调查这些担忧,并努力完善这个想法,希望得到更多的支持。
栈式协程
经过多年的努力,C++标准终于接近于在C++26中添加对栈式协程的基本支持(参见P0876)。有必要深入研究一下堆栈与无堆栈协程。
无栈协程需要编译器的支持,并且不能作为库单独实现。另一方面,堆栈式协程可以自己实现--例如,使用Boost.Context。
前者提供了更有效的内存分配,潜在的更好的编译器优化,以及快速销毁它们的能力。它们也已经在C++20中可用。
后者更容易集成到现有的项目中,因为它们不需要像无栈协程那样完全重写新的习惯用法。事实上,它们完全向用户隐藏了实现细节,使用户能够编写简单的异步线性代码。
无堆叠:
auto data = co_await socket.receive();process(data);co_await socket.send(data);co_return; // requires function to return a special data type
堆叠:
auto data = socket.receive();process(data);socket.send(data);
P0876已被纳入核心亚组。经过讨论,决定禁止在执行线程之间迁移此类协程。禁止的主要原因是编译器优化了对TLS的访问并缓存了TLS变量的值:
thread_local int i = 0;++i;foo(); // Stackful coroutines can switch execution threadsassert(i > 0); // The compiler saved the address in a register; we’re working with the TLS of another thread
摘要
就这么定了!C++23已经被正式发送到更高的ISO权威机构,并将很快作为一个完整的标准发布。
与此同时,C++26的开发正如火如荼,在执行器、网络化、模式匹配、静态反射等方面都有着令人兴奋的前景。