C++20新增部分内容

1、Constraints and concepts (约束和概念)

在类模板和函数模板编程中,主要用于对模板参数的结束和限制,这种约束和限制发生在编译期,编译错误不再那么晦涩难懂了。
在模板编程中,可以限制模板参数的类型或具用某种特性,如:可以限制为整型、数值型、bool 型、或必须支持 hash 特性、或某个类的派生类型等。

在 C++20 中 Concepts 是非常重要的概念,模板编程终于有了质的提升。

Concepts

Concepts 是 requirements 的具名集合,concepts 需要声明在命名空间中,语法如下:

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

如下所示:

template<typename T>
concept Hashable = requires(T a) 
{
    { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};//声明了一个名为Hashable的concept

struct meow {};

template<Hashable T>
void f(T); // 约束这个T必须满足Hashable concept,否则无法编译通过。

int main() 
{
  f("abc"s); // OK,string是可hash的
  f(meow{}); // Error: meow结构体不是可hash的,当然可以让其支持hash。
}

//
template<typename T>
concept C=sizeof(T)>10;

template<C T>
class test{};
template<C T>
void func(T t);

Constraints

约束是逻辑操作和操作数的序列,它用于指定对模板实参的要求。可在 requires 表达式中出现,也可直接作为 concept 的主体。

有三种类型的约束:

  • 合取(conjunction)

  • 析取(disjunction)

  • 原子约束(atomic constraint)

如下所示:

template<Incrementable T>
void f(T) requires Decrementable<T>;

template<Incrementable T>
void f(T) requires Decrementable<T>; // OK:重声明

Requires

requires 用于约束模板参数或具体的参数。

requires 子句

如下所示:

template<typename T>
void f(T&&) requires Eq<T>; // 可作为函数声明符的最末元素出现

template<typename T> 
requires Addable<T> // 或在模板形参列表的右边
T add(T a, T b) 
{ 
    return a + b; 
}

关键词 requires 必须后随某个常量表达式(故可以写为 requires true),但其意图是使用某个具名概念(如上例),或具名概念的一条合取/析取,或一个 requires 表达式。

表达式必须具有下列形式之一:

  • 初等表达式,例如 Swappable、std::is_integral::value、(std::is_object_v && …) 或任何带括号表达式

  • 以运算符 && 连接的初等表达式的序列

  • 以运算符 || 连接的前述表达式的序列

requires 表达式

语法如下:

requires { requirement-seq }    
requires ( parameter-list(optional) ) { requirement-seq }

parameter-list - 与函数声明中类似的形参的逗号分隔列表,但不允许默认实参且不能以(并非指定包展开的)省略号结尾。这些形参无存储期、连接或生存期,它们仅用于辅助进行各个要求的制定。这些形参在要求序列的闭 } 前处于作用域中。
requirement-seq - 要求(requirement)的序列,描述于下(每个要求以分号结尾)。

requirement-seq 中的每个要求必须是下面的四项之一:

  • 简单要求(simple requirement)

  • 类型要求(type requirement)

  • 复合要求(compound requirement)

  • 嵌套要求(nested requirement)

如下所示:

template<typename T>
concept Addable = requires (T x) { x + x; }; // requires 表达式

template<typename T> requires Addable<T> // requires 子句,非 requires 表达式
T add(T a, T b) { return a + b; }

template<typename T>
    requires requires (T x) { x + x; } // 随即的约束,注意关键字被使用两次
T add(T a, T b) { return a + b; }

 

2、Modules (模块)

用于从逻辑上划分代码,能够加快编译速度,并且与导入的顺序无关(还记得以前由于 #include 顺序的不同导致的编译错误吗?)
主要有三个关键字:

  • module:用于声明一个模块

  • export:用于导出模块、函数或类

  • import:用于导入模块

如下所示:
定义了一个 hello world 模块,导出了 hello 函数

//helloworld.cpp
export module helloworld;  // module declaration
import <iostream>;         // import declaration 
export void hello() 
{      
    // export declaration
    std::cout << "Hello world!\n";
}
//main.cpp
import helloworld;

int main()
{
    hello();
}

 

3、Coroutines(协程)

协程,就是能够暂停执行然后在接下来的某个时间点恢复执行的函数,C++中的协程是无栈的(stack less)。使用协程可以方便的编写异步代码(和编写同步代码类似)。

主要涉及三个关键字:

(1)co_await
co_await 暂停当前协程的执行,直到等待的操作完成后继续执行。

task<> tcp_echo_server() 
{
    char data[1024];
    for (;;) 
    {
        std::size_t n = co_await socket.async_read_some(buffer(data)); #与 Python 中的 await 类似
        co_await async_write(socket, buffer(data, n));
    }
}

上述代码,在 async_read_some() 完成后,继续执行下面的语句,在 async_read_some()执行

(2)co_yield
co_yield 暂停执行并返回一个值,与 return 不同的是 co_yield 虽然返回了值 ,但当前函数没有终止。

generator<int> iota(int n = 0) 
{
    while(true)
    {
        co_yield n++;  //与 Python 中的 yield 类似
    }
}

(3)co_return

co_return 用于结束当前协程的执行并返回一个值

lazy<int> f()
{
  co_return 7;
}

当然协程也有一些限制:

  1. 不能使用变长实参;

  2. 不能使用普通的 return 语句,或占位符返回类型(auto 或 Concept);

  3. constexpr 函数、构造函数、析构函数及 main 函数不能是协程。

 

4、Ranges(范围)

提供了处理基于范围的元素(可简单理解为容器)的组件及各种适配器,还有一些新的算法。
主要有如下几类:

  • 基于范围的访问器

  • 基于范围的原语

  • 基于范围的 concept

  • 视图

  • 工厂

  • 适配器

详见头文件:

一个简单的例子:

#include <vector>
#include <ranges>
#include <iostream>
 
int main()
{
    std::vector<int> ints{0,1,2,3,4,5};
    auto even = [](int i){ return 0 == i % 2; };
    auto square = [](int i) { return i * i; };
 
    for (int i : ints | std::views::filter(even) | std::views::transform(square)) 
    {
        std::cout << i << ' ';
    }
}

 

5、Designated Initializers(指定初始化)

使用 {} 初始化数组、类、结构体或联合等的成员。

struct A{int a;int b;int c;};
A a{.a=10,.b=100,.c=20};

operator<=>

三路比较运算符,形如:

lhs <=> rhs

其行为如下:

(a <=> b) < 0 if lhs < rhs
(a <=> b) > 0 if lhs > rhs
(a <=> b) == 0 if lhs equal rhs

示例如下:

#include <compare>
#include <iostream>
int main() 
{
    double foo = -0.0;
    double bar = 0.0;
    auto res = foo <=> bar;
    if (res < 0)
        std::cout << "-0 is less than 0";
    else if (res == 0)
        std::cout << "-0 and 0 are equal";
    else if (res > 0)
        std::cout << "-0 is greater than 0";
}


6、Attributes(特性)

  1. [[nodiscard( string-literal )]]:忽略返回值时警告。

  2. [[likely]] 和[[unlikely]]:指示编译器优化更可能出现的情况或分支。是一种对变量值出现可能性的一种预判。

int f(int i)
{
    if (i < 0) [[unlikely]] 
    {
        return 0;
    }
 
    return 1;
}

    3. [[no_unique_address]]:用于优化存储空间,当成员为空的时候可以不占用存储空间

 

7、Others

(1)constexpr 新增对虚函数的支持。

(2)char8_t 用于存储utf-8的字符串。

(3)constinit

强制常量初始化,不可以动态初始化

const char * g() { return "dynamic initialization"; }
constexpr const char * f(bool p) { return p ? "constant initializer" : g(); }
constinit const char * c = f(true); // OK
constinit const char * d = f(false); // error

(4)labmda

不再支持以值的形式默认捕获参数;
允许以值的形式显示捕获 this;
支持模板,且支持可变参数;

template <typename... Args>
void foo(Args... args) 
{
  [...xs=args]{bar(xs...); // xs is an init-capture pack};
}

(5)std::format

使用 {} 进行格式化字符串,再也不用恶心的 stream 来拼接了,之前使用过boost 的 format,同样好用。

#include <iostream>
#include <format>
 
int main() 
{
    std::cout << std::format("Hello {}!\n", "world");
}

(6)std::span

span 是容器的视图(即不拥有),提供对连续元素组的边界检查访问。因为视图不拥有自己的元素,所以构造和复制的成本很低;

(7)std::jthread

新的线程类,与 std::thread 类似,只是功能更强大,支持停止、自动 join 等

(8)Calendar 和 time zone

(9)endian 用于判断大小端的枚举

(10)std::make_shared 支持数组

(11)atomic 支持浮点数和 smart ptr

(12)std::basic_syncbuf 和 std::basic_osyncstream

(13)string 增加 starts_with 和 end_with 函数

(14)std::atomic_ref 原子引用

(15)std::to_array 将 xxx 转换为 std::array

(16)inline namespace

 

内容节选自连少华《曾被“劝退”的 C++ 20 正式发布!》,本文仅供参考与学习。最近遇到一些好的文章莫名被删除,出于学习和共享的目的,希望留一个备份。如原作者版权需要请联系删除,谢谢!

 

 

上一篇:Docker报错“Dockerfile parse error line 1: FROM requires either one or three arguments”


下一篇:spring transaction management Propagation.REQUIRES_NEW无效