C++23 新特性总结

简介

本文简要介绍了一下 C++23 中标准库部分新增的特性. 为了代码简短, 本文中默认使用了using namespace std;, 实际工作中不推荐这样做.

另外为了更美观的打印, 使用了fmt 库, fmt::命名空间的函数均为其库函数.

关于环境


核心语言层面的改进

Deducing this

语法:

class Object {
 public:
  int GetId(this Object& self) { return self.id_; }

 private:
  int id_ = 0;
};

等价于:

class Object {
 public:
  int GetId() { return id_; }

 private:
  int id_ = 0;
};

该功能可以方便重载运算符

class Object {
 public:
  Object(std::string name) : name_{std::move(name)} {}

  // 此处有三个重载
  const std::string& GetName() const& { return name_; }
  std::string& GetName() & { return name_; }
  std::string&& GetName() && { return std::move(name_); }

 private:
  std::string name_;
};

可以改写为:

class Worker {
public:
    Worker(std::string name) : name_{ std::move(name) } {}

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

private:
    std::string name_;
};

可以参考我的这篇文章: C++23 新特性教程: 如何使用 Deducing this 实现更优代码.

其他参考资料:


if consteval


多维数组下标(Multidimensional Subscript Operator)

#include <array>
#include <cassert>
#include <iostream>

template <typename T, std::size_t Z, std::size_t Y, std::size_t X>
struct Array3d {
  std::array<T, X * Y * Z> m{};

  constexpr T& operator[](std::size_t z, std::size_t y, std::size_t x)  // C++23
  {
    assert(x < X and y < Y and z < Z);
    return m[z * Y * X + y * X + x];
  }
};

int main() {
  // Multidimensional Subscript Operator
  Array3d<int, 4, 4, 4> v;
  std::cout << "v[3, 2, 1] = " << v[3, 2, 1] << '\n';
  v[3, 2, 1] = 42;
  std::cout << "v[3, 2, 1] = " << v[3, 2, 1] << '\n';
}

lambada 表达式增加调用属性(Attribute)

在 C++23 以前, lambda 表达式可以有属性. 如下:

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

在 C++23 中, lambda 的调用可以有属性:

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

进一步参考:


size_t 字面量后缀

已有的数字字面量后缀: U, L, UL, LL, ULL.

新增的数字字面量后缀:

#include <iostream>
#include <vector>

int main() {
  std::vector<int> vec{1, 2, 3, 4, 6};
  for (auto i = 0uz; i < vec.size(); i++) {
    std::cout << vec[i] << ' ';
  }
  static_assert(std::is_same<decltype(10uz), decltype(vec.size())>::value ==
                true);
  static_assert(std::is_same<decltype(10z), ssize_t>::value == true);
}

auto(x): decay copy

当前的auto关键字用于类型推导

auto x = 42;  // x is lvalue

C++23 中新增的auto(x),auto {x}用来创建一个prvalue的副本.

样例:

void process(int&& value) { std::cout << value << std::endl; }
void process_all(const std::vector<int>& data) {
  for (auto& i : data) {
    process(auto(i));
  }
}

增加更多编译宏 #elifdef, #elifndef, #warning

已有的的预处理指令:

C++23 中新增的预处理指令:

#warning 指令用于生成编译器警告信息.

#warning "This is a warning message"


标记不可达代码(std::unreachable())

std::unreachable() 常被用于标记不可达代码. 如果代码执行到这里, 就会产生未定义的行为.

何谓"未定义行为"? 未定义行为是指程序在运行时可能产生任何结果, 包括崩溃, 输出错误, 或者其他不可预测的行为. 取决于编译器的实现. 更多相关内容可以参考: C++ 基础概念: 未定义行为(Undefined Behavior)

#include <utility>

enum CaseOptions : int {
  Option1 = 1,
  Option2 = 2,
  Option3 = 3,
};

int foo(CaseOptions i) {
  switch (i) {
    case Option1:
      return 1;
    case Option2:
      return 2;
    case Option3:
      return 3;
    default:
      std::unreachable();
  }
}

查看汇编结果

foo(CaseOptions):
        mov     eax, edi
        ret

可以看到优化结果是直接返回了i的值. 因为编译器认为default分支是不可达的. 这种函数如果遇到了[1-3]之外的输入则会导致不可预测的结果.

int main(int, const char*[]) {
  std::cout << foo(Option1) << std::endl;
  std::cout << foo(static_cast<CaseOptions>(4)) << std::endl;
}

运行结果

1
685604928 <-- 未定义行为

假设(assume)

C++23 之前,不同编译器有不同的方式来标记假设. 例如:

C++23 统一了这些标记, 引入了std::assume函数.

[[assume(expr)]]

这个标记用来告诉编译器一些前提条件, 以便优化代码.

未加修饰符号

int len(const char* ptr) {
    if (ptr == nullptr) {
        throw "oops";
    }
    return 0;
}

编译生成

.LC0:
        .string "oops"
len(char const*):
        test    rdi, rdi
        je      .L3
        xor     eax, eax
        ret
len(char const*) [clone .cold]:
.L3:
        push    rax
        mov     edi, 8
        call    __cxa_allocate_exception
        xor     edx, edx
        mov     esi, OFFSET FLAT:_ZTIPKc
        mov     QWORD PTR [rax], OFFSET FLAT:.LC0
        mov     rdi, rax
        call    __cxa_throw

assume之后:

int len(const char* ptr) {
    [[assume(ptr != nullptr)]];
    if (ptr == nullptr) {
        throw "oops";
    }
    return 0;
}

编译生成

len(char const*):
        xor     eax, eax
        ret

可以看出整个异常处理代码被优化掉了.

进一步参考:


Unicode 已命名字符转义

C++23 之前, 字符转义只能用于 ASCII 字符.

auto c {U'\u0100'}; // {Latin Capital Letter A with macron}

C++23 引入了已命名字符转义

auto c {U'\N{Latin Capital Letter A with macron}'};

example

#include <cassert>
#include <codecvt>
#include <iostream>
#include <locale>
#include <string>

int main() {
  auto c{U'\u0100'};  // {LATIN CAPITAL LETTER A WITH MACRON}
  auto nc{U'\N{LATIN CAPITAL LETTER A WITH MACRON}'};
  assert(c == nc);
  std::u32string str;

  str.push_back(c);
  str.push_back(nc);
  str.push_back('\n');

  std::locale::global(std::locale(""));

  std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;

  std::cout << converter.to_bytes(str);
}

行拼接符('\')前的空白字符

在 C++23 以前, 如果行拼接符\后有空白字符, 则会出现未定义的行为.

C++23 中, 行拼接符后的空白字符会被忽略.

#include <iostream>

int main() {
  int i = 1
      // 反斜杠后面有个空白符(space) \
    + 1
      ;
  std::cout << i << '\n';
}

注意: 如果要复现请你主动给\后面加一个空格, 直接拷贝当前示例代码不一定会包含空格(这个不受我控制).


标准库部分

字符串格式化改进

C++23 支持在std::print()以及std::println()中格式化字符串.

以下是一个简单的例子:

std::string name{ "C++23" };
// Old-style cout/format() pattern
std::cout << std::format("Hello {}!\n", name);
// C++23 print()
std::print("Hello {}!\n", name);
// C++23 println()
std::println("Hello {}!", name);

目前没有编译器支持. 如果需要使用可以使用fmt库.

vector<pair<int, int>> v { {1, 2}, { 3, 4 } };
println("{}", v);   // [(1, 2), (3, 4)]

set<pair<int, int>> s { {1, 2}, { 3, 4 } };
println("{}", s);   // {(1, 2), (3, 4)}

map<int, int> m { {1, 2}, { 3, 4 } };
println("{}", m);   // {1: 2, 3: 4}

vector<string> strings{ "Hello", "World!\t2023" };
println("{}", strings);        // ["Hello", "World!\t2023"]
println("{::}", strings);      // [Hello, World!    2023]

vector<vector<int>> vv{ {11, 22}, { 33, 44, 55 } };
println("{}", vv);             // [[11, 22], [33, 44, 55]]
println("{:n}", vv);           // [11, 22], [33, 44, 55]
println("{:n:n}", vv);         // 11, 22, 33, 44, 55
println("{:n:n:*^4}", vv);     // *11*, *22*, *33*, *44*, *55*

标准库模块(import std)

C++23 引入了标准库模块(Module), 这是一个新的模块化标准库的方式, 它将标准库分解为多个模块, 每个模块都有自己的接口和实现, 这样可以减少编译时间, 提高构建速度.

import std

import std导入的模块(Module)包括:

import std;

using namespace std;

int main() {
  cout << "Hello from cout!" << endl;
  println("Hello from println!");
  printf("%s\n", "Hello from cstdlib!");
  return 0;
}

将会输出:

Hello from cout!
Hello from std::println!
Hello from cstdlib!

import std.compat

目前没有编译器支持.

除了std 之外会加上全局命名空间的符号.

示例代码:

import std.compat;

int main() {
    std::cout << "Hello from std::cout" << std::endl;
    std::println("Hello from std::println!");
    std::printf("Hello from std::printf\n");

    ::printf("Hello from ::printf\n");
    return 0;
}

basic_string(_view)::contains()

检查字符串中是否含有待查找串.

Example:

#include <fmt/core.h>

#include <string>

int main() {
  std::string haystack{"Hello World!"};
  fmt::println("{}", haystack.contains("World"));  // true
  fmt::println("{}", haystack.contains('!'));      // true
  using namespace std::string_view_literals;
  fmt::println("{}", haystack.contains("Hello"sv));  // true
}

在 Compiler Explorer 运行代码


禁止从 nullptr 构造 string(_view)

在 C++20 及之前的版本中, 以下代码是合法的:

// C++20 and before (undefined behavior)
std::string s { nullptr };

但是在运行时会有未定义行为.

C++23 中禁止了从nullptr构建字符串.

// C++23 (compile-time error)
std::string s { nullptr };

但是还是可以从空指针构建字符串:

const char* p = nullptr;
std::string s{ p };
assert(s.empty()); // runtime assertion

basic_string 新增 resize_and_overwrite(count, op)

考虑一个将字符串pattern重复count次的函数:

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++) {
      result.append(pattern);
   }
   return result;
}

这样的实现不是最优的, 因为:

std::string GeneratePattern(const std::string& pattern, size_t count) {
   std::string result;
   const auto step = pattern.size();
   // GOOD: No initialization
   result.resize_and_overwrite(step * count, [&](char* buf, size_t n) {
      for (size_t i = 0; i < count; i++) {
         // GOOD: No bookkeeping
         memcpy(buf + i * step, pattern.data(), step);
      }
      return step * count;
   });
   return result;
}

std::optional 的链式调用

C++23 中新增了一些支持链式调用的函数:

#include <iostream>
#include <optional>
#include <string>
#include <vector>

using namespace std;

optional<int> Parse(const string& s) {
  try {
    return stoi(s);
  } catch (...) {
    return {};
  }
}

int main() {
  vector<string> inputs{"12", "nan"};
  for (auto& s : inputs) {
    auto result =
        Parse(s)
            .and_then([](int value) -> optional<int> { return value * 2; })
            .transform([](int value) { return to_string(value); })
            .or_else([] { return optional<string>{"No Integer Found"}; });
    cout << *result << endl;
  }
}

在 Compiler Explorer 运行代码


Stacktrace

定义在 <stacktrace> 头文件中. 允许获取和处理堆栈跟踪.

样例代码:

auto trace { std::stacktrace::current() };
std::cout << std::to_string(trace) << std::endl;

g++ 编译时需要加 -lstdc++_libbacktrace 选项. 如果 gcc 版本大于等于 14, 选项改为-lstdc++exp 样例输出:

0# bar() at /workspaces/cmake-project-2024/src/cpp23/stacktrace.cpp:6
1# foo() at /workspaces/cmake-project-2024/src/cpp23/stacktrace.cpp:14
2# main at /workspaces/cmake-project-2024/src/cpp23/stacktrace.cpp:16
3# __libc_start_call_main at :0
4# __libc_start_main_impl at :0
5# _start at :0
6#

在 Compiler Explorer 运行代码

在异常中增加堆栈信息

很多语言都支持在出现异常的时候打印堆栈, 更容易定位出错位置, 方便调试. C++23 中也支持这个功能.

样例代码:

#include <exception>
#include <iostream>
#include <stacktrace>

class Exception : public std::exception {
 public:
  explicit Exception(std::string message,
                     std::stacktrace st = std::stacktrace::current())
      : message_{std::move(message)}, stack_{std::move(st)} {}
  [[nodiscard]] const char* what() const noexcept override {
    return message_.c_str();
  }
  [[nodiscard]] const std::stacktrace& trace() const noexcept { return stack_; }

 private:
  std::string message_;
  std::stacktrace stack_;
};

void bar() { throw Exception{"exception at bar()."}; }
void foo() { bar(); }

int main() {
  try {
    foo();
  } catch (const Exception& e) {
    std::cout << "Exception caught: " << e.what() << std::endl;
    std::cout << "Stacktrace: \n" << std::to_string(e.trace()) << std::endl;
  }
}

在 Compiler Explorer 运行代码

进一步阅读:


Ranges 库的变化

ranges::starts_with() / ranges::ends_with()

检查一个range的开头(或结尾)是否与另一个range匹配.

样例代码:

vector v1{11, 22, 33, 44};
vector v2{11, 22};
fmt::println("{}", ranges::starts_with(v1, v2));  // true
fmt::println("{}", ranges::ends_with(v1, v2));    // false

ranges::shift_left() / ranges::shift_right()

将一个range的元素向左或向右移动. 样例代码:

vector<string> v{"a", "b", "c", "d", "e"};
ranges::shift_left(v, 2);
fmt::println("{}", v);  // "c", "d", "e", "", ""
ranges::shift_right(v, 1);
fmt::println("{}", v);  // "", "c", "d", "e", ""

ranges::to()

将一个range内的元素转储到一个容器.

样例代码:

auto ints = std::views::iota(1, 5)
            | std::views::transform([](const auto& v) { return v * 2; });
auto vec{ std::ranges::to<std::vector>(ints) };
std::print("{}", vec); // [2, 4, 6, 8]

// 从vector 转到set
auto set1{ std::ranges::to<std::set>(vec) };

// 用管道符从set<int> 转到 set<double>
auto set2{ vec | std::ranges::to<std::set<double>>() };

// 使用`from_range`构造函数从vector<int> 转到set<double>
std::set<double> set3{ std::from_range, vec };

ranges::split()

使用views::split()将一个字符串分割成多个子串.

string text{"s1 s2 s3 s4 s5"};

auto words{text | views::split(' ') | views::transform([](const auto& v) {
            return string{from_range, v};
            }) |
            ranges::to<vector>()};

fmt::println("words: {:n:?}", words);  // words: "s1", "s2", "s3", "s4", "s5"

ranges::find_last()系列

查找一个range中的最后一个元素.

返回一个子range的迭代器, 一直到范围结束. 如没有找到则返回{last, last}

样例代码:

vector v{1, 2, 3, 4, 5};
fmt::println("{}", ranges::find_last(v, 3));  // [3, 4, 5]
fmt::println("{}", ranges::find_last_if(
             v, [](int i) { return i % 2 == 0; }));  // [4, 5]
fmt::println("{}", ranges::find_last_if_not(
             v, [](int i) { return i % 2 == 0; }));  // [5]

ranges::contains() / ranges::contains_subrange()

样例代码:

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

一些folding算法

新增了如下算法:

样例代码:

vector v{1, 2, 3, 4, 5};
fmt::println("{}", ranges::fold_left(v, 0, std::plus{})); // 15
fmt::println("{}", std::ranges::fold_left_first(v, std::multiplies<>()).value()); // 120
fmt::println("{}", ranges::fold_right(v, 1, std::multiplies{})); // 120
fmt::println("{}", ranges::fold_right_last(v, std::plus{}).value()); // 15
fmt::println("{}", ranges::fold_left_with_iter(v, 0, std::plus{}).value); // 15
fmt::println("{}", ranges::fold_left_first_with_iter(v, std::multiplies{}).value.value()); // 120

Views 库的变化

views::zip

views::zip是一个新的视图, 它可以将多个视图合并成一个视图.

样例代码:

vector v1{1, 2, 3};
vector v2{'a', 'b', 'c'};
auto r1{views::zip(v1, v2)};
fmt::println("{}", r1); // [(1, 'a'), (2, 'b'), (3, 'c')]

view::zip_transform

views::zip_transform是一个新的视图, 它可以将多个视图的元素应用一个操作符, 生成一个新的视图.

样例代码:

vector v1{1, 2, 3};
vector v2{4, 5, 6};
auto r2{views::zip_transform(multiplies(), v1, v2)};
fmt::println("{}", r2); // [4, 10, 18]

views::adjacent

views::adjacent是一个新的视图, 它可以将一个视图的元素组合成一个元组. views::adjacent: A view with each element a tuple of references to N adjacent elements from the original view

vector v{1, 2, 3, 4};
auto r3{v | views::adjacent<2>};
fmt::println("{}", r3); // [(1, 2), (2, 3), (3, 4)]

views::adjacent_transform

将操作施于相邻的N个元素, 并将结果存储在新的视图中.

样例代码:

vector v{1, 2, 3, 4};
auto r4{v | views::adjacent_transform<2>(multiplies())};
fmt::println("{}", r4); // [2, 6, 12]

views::pairwise & views::pairwise_transform

这两个是工具函数:

样例代码:

vector v2{1, 2, 3, 4};
auto r5{v2 | views::pairwise};
fmt::println("{}", r5); // [(1, 2), (2, 3), (3, 4)]

vector v3{3, 4, 5};
auto r6{v3 | views::pairwise_transform(plus())};
fmt::println("{}", r6); // [7, 9]

views::slide

views::slide是一个新的视图, 它可以将一个视图的元素组合成一个元组.

views::adjacent相似, 但是窗口大小是运行时参数.

样例代码:

vector v{1, 2, 3, 4, 5};
auto r7{v | views::slide(2)};
fmt::println("{}", r7);  // [[1, 2], [2, 3], [3, 4], [4, 5]]

views::chunk

创建一个新的视图, 每 N 个元素组成一个元组.

样例代码:

vector v { 1, 2, 3, 4, 5 };
auto r8 { v | views::chunk(2) };
fmt::println("{}", r8);  // [[1, 2], [3, 4], [5]]

views::chunk_by

创建一个新的视图, 但是每个元组的元素是通过一个谓词来决定的.

样例代码:

vector v{1, 2, 2, 3, 0, 4, 5, 2};
auto r9{v | views::chunk_by(ranges::less_equal{})};
fmt::println("{}", r9);  // {(1,2,2,3),(0,4,5),(2)}

views::join_with

使用给定的分隔符连接一个视图的元素. views::join_with(): Joins elements of a range using a given separator 样例代码:

vector<string> vs{"hi", "you", "!"};
auto str{vs | views::join_with('\n')};
fmt::println("{}", str); // ['h', 'i', '\n', 'y', 'o', 'u', '\n', '!']

views::stride

返回一个视图, 其中的元素是原视图的等间隔的子集.

样例代码:

vector v { 1, 2, 3, 4, 5 };
auto r10 { v | views::stride(2) };
fmt::println("{}", r10);  // {1, 3, 5}

views::repeat

重复一个元素无数次, 或者指定次数.

auto r11 { views::repeat(2, 3) }; // {2, 2, 2}
auto r12 { views::repeat(2) };    // {2, 2, 2, ... }

views::cartesian_product

返回表示 n 个给定范围的笛卡尔积的元组的视图.

样例代码:

vector v = { 1, 2 };
auto r13 { views::cartesian_product(v, v) };
fmt::println("{}", r13); // [(1, 1), (1, 2), (2, 1), (2, 2)]

views::as_rvalue

一个表示底层range的视图, 但是其元素是右值.

vector words{"Hello"s, "World"s, "2023"s};
vector<string> mv;
ranges::copy(words | views::as_rvalue, back_inserter(mv));
fmt::println("{}", mv);  // ["Hello", "World", "2023"]

在 Compiler Explorer 运行代码


std::expected

定义在头文件 <expected>中.

expected<T, E> 包含两个值:

样例代码:

expected<int, string> a { 21 };
expected<int, string> b { unexpected("Some error"s) };

成员函数:

链式调用

#include <expected>
#include <iostream>
#include <string>

using namespace std;

expected<int, string> divide(int a, int b) {
  if (b == 0) {
    return unexpected<string>("Division by zero");
  } else {
    return a / b;
  }
}

int main() {
  auto result =
      divide(12, 2)
          .and_then([](int result) {
            return divide(result, 2);  // Further divide the result by 2
          })
          .or_else([](const string& error) {
            std::cout << "Error: " << error << std::endl;
            return expected<int, std::string>(
                0);  // Return a default value in case of error
          });

  if (result) {
    std::cout << "Result: " << *result << std::endl;
  } else {
    std::cout << "Error: " << result.error() << std::endl;
  }

  return 0;
}

在 Compiler Explorer 运行代码

更详细内容可以参考: 解读 C++23 std::expected 函数式写法


std::move_only_function<>

在 C++23 之前, 这个例子会失败:

int Work(std::function<int()> f) {
   return f();
}
std::cout << Work([p = std::make_unique<int>(42)] { return *p; });
"Attempting to reference a deleted function"
The copy ctor of the std::function tries to copy the lambda, which is not possible due to the captured unique_ptr

C++23:

#include <fmt/core.h>

#include <functional>
#include <iostream>
using namespace std;

int Process(std::move_only_function<int()> f) { return f(); }

int main() {
  cout << Process([] { return 1; }) << endl; // 1
  cout << Process([p = std::make_unique<int>(2)] { return *p; }) << endl; // 2
}

在 Compiler Explorer 运行代码


std::spanstream

定义在头文件 <spanstream>中. 允许在外部缓冲区上使用流操作.

样例代码:

char text[] = "11 22";
int a, b;
ispanstream in{span<char>{text}};
in >> a >> b;
fmt::println("a: {} b: {}", a, b);

char buffer[32]{};
ospanstream out{span<char>{buffer}};
out << 22 << 11;
fmt::println("{}", buffer);

输出

a: 11 b: 22
2211

在 Compiler Explorer 运行代码

查看源码


std::byteswap()

定义在头文件<bit>中. 是交换整数类型字节的标准方法.

样例代码:

std::uint32_t a{ 0x12345678u };
fmt::println("{:x}", a);
std::uint32_t b{ std::byteswap(a) };
fmt::println("{:x}", b);

输出

12345678
78563412

在 Compiler Explorer 运行代码


std::to_underlying()

定义在头文件<utility>中. 作用是将一个枚举转换为其基础类型, 与static_cast<std::underlying_type_t<E>>(enum_value)等效.

样例代码:

#include <cassert>
#include <cstdint>
#include <iostream>
#include <utility>

using namespace std;

enum Options : uint32_t { Opt1 = 1, Opt2 = 2, Opt3 = 3 };

int main() {
  Options r{Options::Opt1};

  auto a{static_cast<underlying_type_t<Options>>(r)};
  auto b{to_underlying(r)};
  assert(a == b);
  cout << a << endl;
  cout << b << endl;
}

在 Compiler Explorer 运行代码


std::flat_(multi)map & std::flat_(multi)set

目前为止没有编译器支持. 如果需要使用可以使用boost库.

std::flat_mapstd::flat_multimap

定义在 <flat_map> 头文件中.

提供类似于 std::map 的接口, 但是不支持重复的键, 并且键是有序的. 键的查询很快, 因为键是有序的.

内部存储是一个连续性的容器, 比如 std::vector 或者 std::deque. keyvalue分别存储在不同的容器上.

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>> myMap;

std::flat_setstd::flat_multiset

定义在 <flat_set> 头文件中. 内部的键存储在一个连续的有序的容器中.

std::flat_set<int> mySet;

std::mdspan

目前为止没有编译器支持.

定义在 <mdspan> 头文件中.

样例代码:

int* data { /* ... */ };

// View data as contiguous memory representing 2 rows of 2 ints each
auto mySpan { std::mdspan(data, 2, 2) };

// Access data from mdspan
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;
   }
}

std::generator

定义在 <generator> 头文件中. 定义了一个标准的协程生成器.

样例代码:

std::generator<int> getSequenceGenerator(int startValue, int numberOfValues) {
   for (int i { startValue }; i < startValue + numberOfValues; ++i) {
      // Yield a value to the caller, and suspend the coroutine.
      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();
   }
}

关联容器异构删除

关联容器已经存在异构查找, C++23 中新增了异构删除和提取, 也就是增加了一个erase(K&&)extract(K&&). 用来提升性能.


参考链接

  1. C++23
  2. C++23: An Overview of Almost All New and Updated Features - Marc Gregoire - CppCon 2023

后记

C++23 中的新特性还有很多, 本文只是列举了一部分. 有些特性还没有得到编译器的支持. 本文只是一个概览, 详细的特性还需要查阅官方文档.

Tags: