• C++ 20语言特性


    Coroutines(协程

    协程是一种特殊的函数,它的执行可以被暂停或恢复。要定义协程,关键字co_return co_await,或co_yield 必须出现在函数体中。c++ 20的协程是无栈的;除非编译器进行了优化,否则它们的状态是在堆上分配的。

    协程的一个例子是generator函数,它在每次调用时生成一个值:

    generator<int> range(int start, int end) {
      while (start < end) {
        co_yield start;
        start++;
      }
    
      // Implicit co_return at the end of this function:
      // co_return;
    }
    
    for (int n : range(0, 10)) {
      std::cout << n << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面的range生成器函数生成的值从start开始直到end(互斥),每个迭代步骤生成存储在start中的当前值。生成器在每次调用range时都保持它的状态(在本例中,调用是针对for循环中的每次迭代)。co_yield接受给定的表达式,生成(即返回)它的值,并在那一点暂停协程。在恢复时,在co_yield之后继续执行。

    协程的另一个例子是task,它是一个在等待任务时执行的异步计算:

    task<void> echo(socket s) {
      for (;;) {
        auto data = co_await s.async_read();
        co_await async_write(s, data);
      }
    
      // Implicit co_return at the end of this function:
      // co_return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在本例中,引入了co_await关键字。这个关键字接受一个表达式,如果您正在等待的东西(在本例中是读或写)没有准备好,则挂起执行,否则继续执行。(注意,在内部,co_yield使用co_await。)

    使用任务惰性地评估一个值:

    task<int> calculate_meaning_of_life() {
      co_return 42;
    }
    
    auto meaning_of_life = calculate_meaning_of_life();
    // ...
    co_await meaning_of_life; // == 42
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意:虽然这些示例说明了如何在基本级别上使用协程,但在编译代码时还有更多内容。这些例子并不意味着完全覆盖c++ 20的协程。由于标准库还没有提供generatortask类,所以我使用cppcoro库来编译这些示例。

    Concepts(概念)

    概念被命名为约束类型的编译时谓词。它们的形式如下:

    template < template-parameter-list >
    concept concept-name = constraint-expression;
    
    • 1
    • 2

    其中constraint-expression计算为constexpr布尔值。约束应该对语义需求进行建模,例如类型是数字类型还是可哈希类型。如果给定的类型不满足它所绑定的概念(例如:“约束表达式”返回“false”)。因为约束是在编译时计算的,所以它们可以提供更有意义的错误消息和运行时安全性。

    // `T` is not limited by any constraints.
    template <typename T>
    concept always_satisfied = true;
    // Limit `T` to integrals.
    template <typename T>
    concept integral = std::is_integral_v<T>;
    // Limit `T` to both the `integral` constraint and signedness.
    template <typename T>
    concept signed_integral = integral<T> && std::is_signed_v<T>;
    // Limit `T` to both the `integral` constraint and the negation of the `signed_integral` constraint.
    template <typename T>
    concept unsigned_integral = integral<T> && !signed_integral<T>;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    有各种各样的语法形式来加强概念:

    // Forms for function parameters:
    // `T` is a constrained type template parameter.
    template <my_concept T>
    void f(T v);
    
    // `T` is a constrained type template parameter.
    template <typename T>
      requires my_concept<T>
    void f(T v);
    
    // `T` is a constrained type template parameter.
    template <typename T>
    void f(T v) requires my_concept<T>;
    
    // `v` is a constrained deduced parameter.
    void f(my_concept auto v);
    
    // `v` is a constrained non-type template parameter.
    template <my_concept auto v>
    void g();
    
    // Forms for auto-deduced variables:
    // `foo` is a constrained auto-deduced value.
    my_concept auto foo = ...;
    
    // Forms for lambdas:
    // `T` is a constrained type template parameter.
    auto f = []<my_concept T> (T v) {
      // ...
    };
    // `T` is a constrained type template parameter.
    auto f = []<typename T> requires my_concept<T> (T v) {
      // ...
    };
    // `T` is a constrained type template parameter.
    auto f = []<typename T> (T v) requires my_concept<T> {
      // ...
    };
    // `v` is a constrained deduced parameter.
    auto f = [](my_concept auto v) {
      // ...
    };
    // `v` is a constrained non-type template parameter.
    auto g = []<my_concept auto v> () {
      // ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    requires关键字可以用来启动一个require子句或一个require表达式:

    template <typename T>
      requires my_concept<T> // `requires` clause.
    void f(T);
    
    template <typename T>
    concept callable = requires (T f) { f(); }; // `requires` expression.
    
    template <typename T>
      requires requires (T x) { x + x; } // `requires` clause and expression on same line.
    T add(T a, T b) {
      return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    注意,requires表达式中的参数列表是可选的。require表达式中的每个需求都是下列要求之一:

    • Simple requirements - 断言给定表达式是否有效。
    template <typename T>
    concept callable = requires (T f) { f(); };
    
    • 1
    • 2
    • Type requirements - 关键字typename后跟一个类型名表示,断言给定的类型名是有效的。
    struct foo {
      int foo;
    };
    
    struct bar {
      using value = int;
      value data;
    };
    
    struct baz {
      using value = int;
      value data;
    };
    
    // Using SFINAE, enable if `T` is a `baz`.
    template <typename T, typename = std::enable_if_t<std::is_same_v<T, baz>>>
    struct S {};
    
    template <typename T>
    using Ref = T&;
    
    template <typename T>
    concept C = requires {
                         // Requirements on type `T`:
      typename T::value; // A) has an inner member named `value`
      typename S<T>;     // B) must have a valid class template specialization for `S`
      typename Ref<T>;   // C) must be a valid alias template substitution
    };
    
    template <C T>
    void g(T a);
    
    g(foo{}); // ERROR: Fails requirement A.
    g(bar{}); // ERROR: Fails requirement B.
    g(baz{}); // PASS.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • Compound requirements - 用大括号括起来的表达式,后面跟着返回类型或类型约束。
    template <typename T>
    concept C = requires(T x) {
      {*x} -> typename T::inner; // the type of the expression `*x` is convertible to `T::inner`
      {x + 1} -> std::same_as<int>; // the expression `x + 1` satisfies `std::same_as`
      {x * 1} -> T; // the type of the expression `x * 1` is convertible to `T`
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • Nested requirements - 由requires关键字表示,指定额外的约束(例如本地参数参数)。
    template <typename T>
    concept C = requires(T x) {
      requires std::same_as<sizeof(x), size_t>;
    };
    
    • 1
    • 2
    • 3
    • 4

    Designated initializers(指定初始化式)

    c风格指定初始化式语法。任何未显式列出在指定初始化列表中的成员字段都是默认初始化的。

    struct A {
      int x;
      int y;
      int z = 123;
    };
    
    A a {.x = 1, .z = 2}; // a.x == 1, a.y == 0, a.z == 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Template syntax for lambdas(lambda的模板语法)

    在lambda表达式中使用熟悉的模板语法。

    auto f = []<typename T>(std::vector<T> v) {
      // ...
    };
    
    • 1
    • 2
    • 3

    Range-based for loop with initializer(带初始化器的基于范围的for循环)

    该特性简化了常见的代码模式,有助于保持范围紧凑,并为常见的生存期问题提供了优雅的解决方案。

    for (std::vector v{1, 2, 3}; auto& e : v) {
      std::cout << e;
    }
    // prints "123"
    
    • 1
    • 2
    • 3
    • 4

    likely and unlikely attributes(可能和不可能的属性)

    向优化器提供提示,说明已标记语句执行的概率很高。

    switch (n) {
    case 1:
      // ...
      break;
    
    [[likely]] case 2:  // n == 2 is considered to be arbitrarily more
      // ...            // likely than any other value of n
      break;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如果一个可能/不太可能的属性出现在If语句的右括号之后,则表明分支可能/不太可能执行其子语句(体)。

    int random = get_random_number_between_x_and_y(0, 3);
    if (random > 0) [[likely]] {
      // body of if statement
      // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    它也可以应用于迭代语句的子语句(体)。

    while (unlikely_truthy_condition) [[unlikely]] {
      // body of while statement
      // ...
    }
    
    • 1
    • 2
    • 3
    • 4

    Deprecate implicit capture of this(不建议隐式捕获)

    在lamdba捕获中使用[=]隐式捕获this现在已弃用;更喜欢使用[=,this][=,*this]显式捕获。

    struct int_value {
      int n = 0;
      auto getter_fn() {
        // BAD:
        // return [=]() { return n; };
    
        // GOOD:
        return [=, *this]() { return n; };
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Class types in non-type template parameters(非类型模板形参中的类型)

    类现在可以在非类型模板参数中使用。作为模板参数传入的对象的类型为const T,其中T是对象的类型,并且具有静态存储时间。

    struct foo {
      foo() = default;
      constexpr foo(int) {}
    };
    
    template <foo f>
    auto get_foo() {
      return f;
    }
    
    get_foo(); // uses implicit constructor
    get_foo<foo{123}>();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    constexpr virtual functions(constexpr虚函数)

    虚函数现在可以是constexpr并在编译时计算。constexpr虚函数可以覆盖非constexpr虚函数,反之亦然。

    struct X1 {
      virtual int f() const = 0;
    };
    
    struct X2: public X1 {
      constexpr virtual int f() const { return 2; }
    };
    
    struct X3: public X2 {
      virtual int f() const { return 3; }
    };
    
    struct X4: public X3 {
      constexpr virtual int f() const { return 4; }
    };
    
    constexpr X4 x4;
    x4.f(); // == 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    explicit(bool)(是否显式)

    在编译时有条件地选择构造函数是否显式。explicit(true)与指定explicit相同。

    struct foo {
      // Specify non-integral types (strings, floats, etc.) require explicit construction.
      template <typename T>
      explicit(!std::is_integral_v<T>) foo(T) {}
    };
    
    foo a = 123; // OK
    foo b = "123"; // ERROR: explicit constructor is not a candidate (explicit specifier evaluates to true)
    foo c {"123"}; // OK
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Immediate functions

    类似于constexpr函数,但是带有consteval说明符的函数必须产生一个常量。这些被称为“直接函数”。

    consteval int sqr(int n) {
      return n * n;
    }
    
    constexpr int r = sqr(100); // OK
    int x = 100;
    int r2 = sqr(x); // ERROR: the value of `x` is not usable in a constant expression
                     // OK if `sqr` were a `constexpr` function
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    using enum

    将枚举成员引入作用域以提高可读性。之前:

    enum class rgba_color_channel { red, green, blue, alpha };
    
    std::string_view to_string(rgba_color_channel channel) {
      switch (channel) {
        case rgba_color_channel::red:   return "red";
        case rgba_color_channel::green: return "green";
        case rgba_color_channel::blue:  return "blue";
        case rgba_color_channel::alpha: return "alpha";
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    之后:

    enum class rgba_color_channel { red, green, blue, alpha };
    
    std::string_view to_string(rgba_color_channel my_channel) {
      switch (my_channel) {
        using enum rgba_color_channel;
        case red:   return "red";
        case green: return "green";
        case blue:  return "blue";
        case alpha: return "alpha";
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Lambda capture of parameter pack(参数包的Lambda捕获)

    捕获参数按值包:

    template <typename... Args>
    auto f(Args&&... args){
        // BY VALUE:
        return [...args = std::forward<Args>(args)] {
            // ...
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    通过引用捕获参数包:

    template <typename... Args>
    auto f(Args&&... args){
        // BY REFERENCE:
        return [&...args = std::forward<Args>(args)] {
            // ...
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    char8_t

    提供表示UTF-8字符串的标准类型。

    char8_t utf8_str[] = u8"\u0123";
    
    • 1

    c++ 20库功能

    Concepts library

    标准库还提供了用于构建更复杂概念的概念。其中包括:

    核心语言概念:

    • same_as - 指定两种相同的类型。
    • derived_from - 指定一个类型派生自另一个类型。
    • convertible_to - 指定一个类型可隐式转换为另一个类型。
    • common_with - 指定两个类型共享一个公共类型。
    • integral - 指定类型为整型。
    • default_constructible - 指定可以默认构造类型的对象。

    Comparison concepts:

    • boolean - 指定可在布尔上下文中使用的类型。
    • equality_comparable - 指定operator==是一个等价关系。

    Object concepts:

    • movable - 指定可移动和交换某一类型的对象。
    • copyable - 指定可复制、移动和交换某一类型的对象。
    • semiregular - 指定某个类型的对象可以被复制、移动、交换和默认构造。
    • regular - 指定类型为regular,即既为semiregular又为equality_comparable

    Callable concepts:

    • invocable - 指定可使用给定参数类型集调用可调用类型。
    • predicate - 指定可调用类型是布尔谓词。

    Synchronized buffered outputstream

    缓冲包装输出流的输出操作,以确保同步(即输出没有交错)。

    std::osyncstream{std::cout} << "The value of x is:" << x << std::endl;
    
    • 1

    std::span

    span是容器的一个视图(即非所有者视图),它提供了对连续元素组的边界检查访问。由于视图不拥有它们自己的元素,它们的构造和复制成本很低——考虑视图的一种简单方法是它们持有对其数据的引用。跨度可以是动态大小的,也可以是固定大小的。

    void f(std::span<int> ints) {
        std::for_each(ints.begin(), ints.end(), [](auto i) {
            // ...
        });
    }
    
    std::vector<int> v = {1, 2, 3};
    f(v);
    std::array<int, 3> a = {1, 2, 3};
    f(a);
    // etc.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    示例:与维护指针和长度字段不同,span将这两个字段打包在一个容器中。

    constexpr size_t LENGTH_ELEMENTS = 3;
    int* arr = new int[LENGTH_ELEMENTS]; // arr = {0, 0, 0}
    
    // Fixed-sized span which provides a view of `arr`.
    std::span<int, LENGTH_ELEMENTS> span = arr;
    span[1] = 1; // arr = {0, 1, 0}
    
    // Dynamic-sized span which provides a view of `arr`.
    std::span<int> d_span = arr;
    span[0] = 1; // arr = {1, 1, 0}
    constexpr size_t LENGTH_ELEMENTS = 3;
    int* arr = new int[LENGTH_ELEMENTS];
    
    std::span<int, LENGTH_ELEMENTS> span = arr; // OK
    std::span<double, LENGTH_ELEMENTS> span2 = arr; // ERROR
    std::span<int, 1> span3 = arr; // ERROR
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Bit operations

    c++ 20提供了一个新的头,它提供了一些位操作,包括popcount。

    std::popcount(0u); // 0
    std::popcount(1u); // 1
    std::popcount(0b1111`0000u); // 4
    
    • 1
    • 2
    • 3

    Math constants

    header中定义的数学常量,包括PI、欧拉数等。

    std::numbers::pi; // 3.14159...
    std::numbers::e; // 2.71828...
    
    • 1
    • 2

    std::is_constant_evaluated

    谓词函数,当它在编译时上下文中被调用时为真

    constexpr bool is_compile_time() {
        return std::is_constant_evaluated();
    }
    
    constexpr bool a = is_compile_time(); // true
    bool b = is_compile_time(); // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    std::make_shared supports arrays

    auto p = std::make_shared<int[]>(5); // pointer to `int[5]`
    // OR
    auto p = std::make_shared<int[5]>(); // pointer to `int[5]`
    
    • 1
    • 2
    • 3

    starts_with and ends_with on strings

    字符串(和字符串视图)现在有starts_withends_with成员函数来检查一个字符串是否以给定的字符串开始或结束。

    std::string str = "foobar";
    str.starts_with("foo"); // true
    str.ends_with("baz"); // false
    
    • 1
    • 2
    • 3

    Check if associative container has element

    像集合和映射这样的关联容器有一个“contains”成员函数,它可以用来代替“查找和检查迭代器的结束”习惯用法。

    std::map<int, char> map {{1, `a`}, {2, `b`}};
    map.contains(2); // true
    map.contains(123); // false
    
    std::set<int> set {1, 2, 3};
    set.contains(2); // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    std::bit_cast

    将对象从一种类型重新解释为另一种类型的更安全的方法。

    float f = 123.0;
    int i = std::bit_cast<int>(f);
    
    • 1
    • 2

    std::midpoint

    安全地计算两个整数的中点(不溢出)。

    std::midpoint(1, 3); // == 2
    
    • 1

    std::to_array

    将给定的数组/"array-like"对象转换为std::array

    std::to_array("foo"); // returns `std::array`
    std::to_array<int>({1, 2, 3}); // returns `std::array`
    
    int a[] = {1, 2, 3};
    std::to_array(a); // returns `std::array`
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    猿创征文 | H5 API之web存储、拖拽事件以及跨文档通信
    UI自动化测试单例实现报错:AttributeError: ‘NoneType‘ object has no attribute ‘get_driver‘
    旅游APP外包开发注意事项
    yolov5+bytetrack算法在华为NPU上进行端到端开发
    设计模式之创建型模式---工厂模式
    二叉树的Morris遍历
    docker——redis5.0构建的redis-cluster环境
    第三范式
    win10添加回环网卡步骤
    mysql8.0笔记
  • 原文地址:https://blog.csdn.net/qq_55125921/article/details/126531916