C++20新特性

concept
requires
constinit
consteval
co_await
co_return
co_yield
char8_t

优点:
1)没有头文件;
2)声明实现仍然可分离, 但非必要;
3)可以显式指定导出哪些类或函数;
4)不需要头文件重复引入宏 (include guards);
5)模块之间名称可以相同,并且不会冲突;
6)模块只处理一次, 编译更快 (头文件每次引入都需要处理);
7)预处理宏只在模块内有效;
8)模块的引入与引入顺序无关。

例子:
Module code (test.ixx)

// export module xxx.yyy.zzz
export module cpp20.test;

// Failed to import lib
//import std.core;
//import std.filesystem;

/////////////////////////////////////////////
// common export
export auto GetCommonString() {
    return "Welcome to learn C++20!";
}

auto GetSpecificString() {
    return "Welcome to learn C++20!";
}

// error.Because std::core has been not imported
//export std::string GetString() {
//    return "GetString.";
//}

/////////////////////////////////////////////

/////////////////////////////////////////////
// entirety export
export namespace test {
    auto GetTestString() {
        return "Test Test Test!!!";
    }

    template <typename t>
    T Sum(T t)
    {
        return t;
    }
    template <typename t, typename ...args>
    T Sum(T one, Args ...args)
    {
        return one + Sum<t>(args...);
    }

    enum class ValueType
    {
        kBool=0,
        kChar,
        kInt,
        kFloat,
        kDouble,
    };

    template <typename t>
    T GetDataType(ValueType type) {
        switch (type)
        {
        using enum ValueType;
        case kBool:
            return true;
        case kChar:
            return 'A';
        case kInt:
            return 5;
        case kFloat:
            return 12.257902012398877;
        case kDouble:
            return 12.257902012398877;
        }
        return true;
    }

}// namespace test
/////////////////////////////////////////////

/////////////////////////////////////////////
// struct export
export namespace StructTest {
    struct Grade {
        int val = 0;
        int level = 5;
    };

    void AddVal(Grade& g, int num) {
        g.val += num;
        g.level += num;
    }
}// namespace StructTest
/////////////////////////////////////////////
</typename></t></typename></typename>

Calling code (main.cpp)

import cpp20.test;

int main(int argc, char* argv[]) {
    auto ret1 = GetCommonString();
    //auto ret2 = GetSpecificString(); // error
    //auto ret2 = GetString(); // error

    using namespace test;
    auto ret3 = GetTestString();
    auto ret4 = Sum<int>(3, 4);
    auto ret5 = Sum<double>(3.14, 4.62, 9.14);

    auto ret6 = GetDataType<bool>(ValueType::kBool);
    auto ret7 = GetDataType<int>(ValueType::kInt);
    auto ret8 = GetDataType<char>(ValueType::kChar);

    StructTest::Grade grade;
    StructTest::AddVal(grade, 10);
    std::cout << grade.val << " | " << grade.level << std::endl;

    return 0;
}
</char></int></bool></double></int>

总结
1)模块的命名可选择格式为:xxx.yyy.zzz
2)在MSVC编译器中,模块文件是.ixx,而不是.cpp。(.ixx文件后缀是MSVC编译器中默认的模块接口,C++ 模块接口单元)
3)普通函数的导出必须添加”export”;否则,外部无法调用。也可以选择导出命名空间块。详情可参见上述的例子。
4)标准库core和文件系统filesystem,提供的函数无法被识别。可能是本人导入module配置操作有误吧!
5)模块当前支持基本数据类型,比如bool、int、float、char等。也支持结构与类的使用。
6)模块的定义没有头文件、不会重复编译输出、导入模块没有次序区别,因此建议模块的编写添加相应的命名空间,降低相同符号的可能性。

详情例子请参见:

进程:操作系统资源分配的基本单元。调度涉及到用户空间和内核空间的切换,资源消耗较大。
线程:操作系统运行的基本单元。在同一个进程资源的框架下,实现抢占式多任务,相对进程,降低了执行单元切换的资源消耗。
协程:和线程非常类似。但是转变一个思路实现协作式多任务,由用户来实现协作式调度,即主动交出控制权(或称为用户态的线程)。

到底什么是协程?

简单来说,协程就是一种特殊的函数,它可以在函数执行到某个地方的时候暂停执行,返回给调用者或恢复者(可以有一个返回值),并允许随后从暂停的地方恢复继续执行。
请注意,这个暂停执行不是指将函数所在的线程暂停执行,而是单纯的暂停执行函数本身。
说白了,用处就是将”异步”风格的编程”同步”化。

C++20 协程的特点

1)不需要内部栈分配,仅需要一个调用栈的顶层桢。
2)协程运行过程中,需要使用关键词来控制运行过程(比如co_return)。
3)协程可能分配不同线程,触发资源竞争。
4)没有调度器,但是需要标准和编译器的支持。

协程的特点在于是一个线程执行,那和多线程相比,协程有何优势?
优点:
1)极高的执行效率:因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销。和多线程比,线程数量越多,协程的性能优势就越明显。
2)不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

缺点:
1)无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上。当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
2)进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序。

协程中的关键字

co_wait: 挂起协程, 等待其它计算完成。
co_return: 从协程返回 (协程 return 禁止使用)。
co_yield: 返回一个结果并且挂起当前的协程, 下一次调用继续协程的运行。
注意:上述的协程关键字只能在协程中使用。这也就意味着,在main函数中直接调用co_await xxxx(); 是不行的。

如何定义与使用协程?

先了解几个基本的概念:
1)一个线程只能有一个协程;
2)协程函数需要返回值是Promise;
3)协程的所有关键字必须在协程函数中使用;
4)在协程函数中可以按照同步的方式去调用异步函数,只需要将异步函数包装在Awaitable类中,使用co_wait关键字调用即可。

理解了以上概念后,就可以按照特定的规则创建和使用协程:
1)在一个线程中同一个时间只调用一个协程函数,即只有一个协程函数执行完毕了,再去调用另一个协程函数;
2)使用Awatiable类包装所有的异步函数,一个异步函数处理一请求中的一部分工作(比如执行一次SQL查询,或者执行一次http请求等);
3)在对应的协程函数中按照需要,通过增加co_wait关键字同步的调用这些异步函数。注意一个异步函数(包装好的Awaiable类)可以在多个协程函数中调用,协程函数可能在多个线程中被调用(虽然一个线程同一时间只调用一个协程函数),所以最好保证Awaiable类是线程安全的,避免出现需要加锁的情况;
4)在线程中通过调用不同的协程函数响应不同的请求。

协程一般需要定义三个东西:协程体(coroutine),协程承诺特征类型(Traits),await对象(await)。
C++20 协程模板:(仅供参考,非官方标准)

#include <thread>
#include <coroutine>
#include <functional>
#include <windows.h>

// &#x7ED9;&#x534F;&#x7A0B;&#x4F53;&#x4F7F;&#x7528;&#x7684;&#x627F;&#x8BFA;&#x7279;&#x5F81;&#x7C7B;&#x578B;
struct  CoroutineTraits {        // &#x540D;&#x79F0;&#x81EA;&#x5B9A;&#x4E49; |Test|
    struct promise_type {        //&#x540D;&#x79F0;&#x5FC5;&#x987B;&#x4E3A; |promise_type|
        // &#x5FC5;&#x987B;&#x5B9E;&#x73B0;&#x6B64;&#x63A5;&#x53E3;&#x3002;(&#x534F;&#x7A0B;&#x4F53;&#x88AB;&#x521B;&#x5EFA;&#x65F6;&#x88AB;&#x8C03;&#x7528;)
        auto get_return_object() {
            return CoroutineTraits{};
        };

        // &#x5FC5;&#x987B;&#x5B9E;&#x73B0;&#x6B64;&#x63A5;&#x53E3;, &#x8FD4;&#x56DE;&#x503C;&#x5FC5;&#x987B;&#x4E3A;awaitable&#x7C7B;&#x578B;&#x3002;(get_return_object&#x4E4B;&#x540E;&#x88AB;&#x8C03;&#x7528;)
        auto initial_suspend() {
            return std::suspend_never{};   // never&#x8868;&#x793A;&#x7EE7;&#x7EED;&#x8FD0;&#x884C;
            //return std::suspend_always{}; // always&#x8868;&#x793A;&#x534F;&#x7A0B;&#x6302;&#x8D77;
        }

        // &#x5FC5;&#x987B;&#x5B9E;&#x73B0;&#x6B64;&#x63A5;&#x53E3;, &#x8FD4;&#x56DE;&#x503C;&#x5FC5;&#x987B;&#x4E3A;awaitable&#x7C7B;&#x578B;&#x3002;(return_void&#x4E4B;&#x540E;&#x88AB;&#x8C03;&#x7528;)
        // MSVC&#x9700;&#x8981;&#x58F0;&#x660E;&#x4E3A;noexcept&#xFF0C;&#x5426;&#x5219;&#x62A5;&#x9519;
        auto final_suspend() noexcept {
            return std::suspend_never{};
        }

        // &#x5FC5;&#x987B;&#x5B9E;&#x73B0;&#x6B64;&#x63A5;&#x53E3;, &#x7528;&#x4E8E;&#x5904;&#x7406;&#x534F;&#x7A0B;&#x51FD;&#x6570;&#x5185;&#x90E8;&#x629B;&#x51FA;&#x9519;&#x8BEF;
        void unhandled_exception() {
            std::terminate();
        }

        // &#x5982;&#x679C;&#x534F;&#x7A0B;&#x51FD;&#x6570;&#x5185;&#x90E8;&#x65E0;&#x5173;&#x952E;&#x5B57;co_return&#x5219;&#x5FC5;&#x987B;&#x5B9E;&#x73B0;&#x6B64;&#x63A5;&#x53E3;&#x3002;(&#x534F;&#x7A0B;&#x4F53;&#x6267;&#x884C;&#x5B8C;&#x4E4B;&#x540E;&#x88AB;&#x8C03;&#x7528;)
        void return_void() {}

        // &#x6CE8;&#x610F;&#xFF1A;|return_void|&#x548C;|return_value| &#x662F;&#x4E92;&#x65A5;&#x7684;&#x3002;
        // &#x5982;&#x679C;&#x534F;&#x7A0B;&#x51FD;&#x6570;&#x5185;&#x90E8;&#x6709;&#x5173;&#x952E;&#x5B57;co_return&#x5219;&#x5FC5;&#x987B;&#x5B9E;&#x73B0;&#x6B64;&#x63A5;&#x53E3;&#x3002;(&#x534F;&#x7A0B;&#x4F53;&#x6267;&#x884C;&#x5B8C;&#x4E4B;&#x540E;&#x88AB;&#x8C03;&#x7528;)
        //void return_value() {}

        // &#x5982;&#x679C;&#x534F;&#x7A0B;&#x51FD;&#x6570;&#x5185;&#x90E8;&#x6709;&#x5173;&#x952E;&#x5B57;co_yield&#x5219;&#x5FC5;&#x987B;&#x5B9E;&#x73B0;&#x6B64;&#x63A5;&#x53E3;, &#x8FD4;&#x56DE;&#x503C;&#x5FC5;&#x987B;&#x4E3A;awaitable&#x7C7B;&#x578B;
        auto yield_value(int value) {
            // _valu=value;     // &#x53EF;&#x5BF9;|value|&#x505A;&#x4E00;&#x4E9B;&#x4FDD;&#x5B58;&#x6216;&#x5176;&#x4ED6;&#x5904;&#x7406;
            return std::suspend_always{};
        }
    };
};

// &#x534F;&#x7A0B;&#x4F7F;&#x7528;&#x7684;await&#x5BF9;&#x8C61;
struct CoroutineAwaitObj {  // &#x540D;&#x79F0;&#x81EA;&#x5B9A;&#x4E49; |CoroutineAwaitObj|
    // await&#x662F;&#x5426;&#x5DF2;&#x7ECF;&#x8BA1;&#x7B97;&#x5B8C;&#x6210;&#xFF0C;&#x5982;&#x679C;&#x8FD4;&#x56DE;true&#xFF0C;&#x5219;co_await&#x5C06;&#x76F4;&#x63A5;&#x5728;&#x5F53;&#x524D;&#x7EBF;&#x7A0B;&#x8FD4;&#x56DE;
    bool await_ready() const {
        return false;
    }

    // await&#x5BF9;&#x8C61;&#x8BA1;&#x7B97;&#x5B8C;&#x6210;&#x4E4B;&#x540E;&#x8FD4;&#x56DE;&#x7ED3;&#x679C;
    std::string await_resume() const {
        return _result;
    }

    // await&#x5BF9;&#x8C61;&#x771F;&#x6B63;&#x8C03;&#x5F02;&#x6B65;&#x6267;&#x884C;&#x7684;&#x5730;&#x65B9;&#xFF0C;&#x5F02;&#x6B65;&#x5B8C;&#x6210;&#x4E4B;&#x540E;&#x901A;&#x8FC7;handle.resume()&#x6765;&#x662F;await&#x8FD4;&#x56DE;
    void await_suspend(const std::coroutine_handle<> handle) {
        std::jthread([handle, this]() {
            // &#x5176;&#x4ED6;&#x64CD;&#x4F5C;&#x5904;&#x7406;
            _result = "Test";

            // &#x6062;&#x590D;&#x534F;&#x7A0B;
            handle.resume();
                     }).detach();
    }

    // &#x5C06;&#x8FD4;&#x56DE;&#x503C;&#x5B58;&#x5728;&#x8FD9;&#x91CC;
    std::string _result;
};

// &#x534F;&#x7A0B;&#x4F53;
// |CoroutineTraits| &#x5E76;&#x4E0D;&#x662F;&#x8FD4;&#x56DE;&#x503C;&#xFF0C;&#x800C;&#x662F;&#x534F;&#x7A0B;&#x7684;&#x7279;&#x5F81;&#x7C7B;&#x578B;&#xFF1B;&#x4E0D;&#x53EF;&#x4EE5;&#x662F;void&#x3001;string&#x7B49;&#x8FD4;&#x56DE;&#x7C7B;&#x578B;
CoroutineTraits CoroutineFunc() {
    std::cout << "Start CoroutineFunc" << std::endl;

    auto ret = co_await CoroutineAwaitObj();
    std::cout << "Return:" << ret << std::endl;

    std::cout << "Finish CoroutineFunc" << std::endl;
}

int main(int argc, char* argv[]) {
    CoroutineFunc();

    Sleep(10*1000);

    return 0;
}
</windows.h></functional></coroutine></thread>

协程的执行流程

解析:
1)先执行结构体promise_type() ,创建一个promise对象;
2)通过promise对象, 执行get_return_object(), 产生一个coroutine_name对象, 并记录handle;
3)执行initial_suspend(), 根据返回值的await_ready()返回值 判断是否立即执行协程函数, 当返回值中await_ready()返回值为ture则立即执行协程函数, 否则调用返回值的await_suspend挂起协程、跳出到主函数。
我们这里返回值是std::suspend_always,它的await_ready()始终返回false。
4)在主函数中调用协程函数CoroutineFunc(),同时将执行权传递给协程函数;
5)执行协程函数中操作,直到执行 co_waitco_returnco_yield
6)执行awaitable类中的函数await_ready(),根据返回值判断是否将执行权传递给主函数。如果返回值的await_ready返回false,则调用await_suspend();若await_ready返回true,直接执行await_resume(),并返回到主函数(即await_suspend()不被执行);
7)协程函数已经执行完语句,所以准备返回,这里没有co_return,所以调用的是return_void()函数。如果有co_return,则调用的是return_value()函数;
8)然后调用final_suspend,协程进行收尾动作。根据final_suspend的返回值的await_ready判断是否立即析构promise对象,返回true则立即析构,否则不立即析构、将执行权交给主函数; 请注意:如果是立即析构promise对象,则后续主函数无法通过promise获得相应的值。
9)返回主函数执行其他操作,return 0。

协程的储存空间与生命周期

1)C++20 的设计是无栈协程, 所有的局部状态都储存在堆上。参见:有栈协程与无栈协程
2)储存协程的状态需要分配空间,分配 frame 的时候,首先会搜索 promise_type 是否有提供 operator new, 然后再搜索全局范围;
3)也会存在储存分配失败的情况。比如:如果写了 get_return_object_on_allocation_failure() 函数, 那就是失败后的办法, 代替 get_return_object() 来完成工作(可添加关键字 noexcept);
4)协程结束以后的释放空间也会优先在 promise_type 里面搜索 operator delete, 其次再搜索全局范围.

5)协程的储存空间只有在运行完 final_suspend 之后才会析构, 或者人为显式地调用 handle.destroy(). 否则协程的存储空间就永远不会释放;如果在 final_suspend 那里停下了, 那么就得在包装函数里面手动调用 handle.destroy(), 否则就会出现内存泄漏。
6)如果已经运行完毕了 final_suspend, 或者已经被 handle.destroy() 给析构了, 那么协程的储存空间已经被释放了;如果再次对 handle 做任何的操作都会导致段错误。

协程用例请参考:

范围(ranges):是”项目集合”或”可迭代事物”的抽象。最基本的定义只需要存在begin()和end()在范围内。Range 代表一串元素, 或者一串元素中的一段类似 begin/end 对。
视图(view):其意义可以参考string_view,它的拷贝代价是很低的,需要拷贝的时候直接传值即可,不必传引用。
ranges通过增加了一种叫做view(视图)的概念,实现了Lazy Evaluation(惰性求值),并且可以将各种view的关系转化用符号”|”串联起来。
范围适配器(range adaptor):可以将一个range转换为一个view(也可以将一个view转换为另一个view)。范围适配器接受 viewable_range 为其第一参数并返回一个 view 。

常见的范围适配器:

适配器 描述 views::filter 由 range 中满足某个谓词的元素构成的 view views::transform 对序列的每个元素应用某个变换函数的 view views::take 由另一 view 的前 N 个元素组成的 view views::join 由拉平 range 的 view 所获得的序列构成的 view views::elements 选取仿 tuple 值组成的 view 和数值 N ,产生每个 tuple 的第 N 个元素的 view views::drop 由另一 view 跳过首 N 个元素组成的 view views::all 包含 range 的所有元素的 view views::take_while 由另一 view 的到首个谓词返回 false 为止的起始元素组成的 view views::drop_while 由另一 view 跳过元素的起始序列,直至首个谓词返回 false 的元素组成的 view views::split 用某个分隔符切割另一 view 所获得的子范围的 view views::common 转换 view 为 common_range views::reverse 以逆序迭代另一双向视图上的元素的 view views::istream_view 由在关联的输入流上相继应用 operator>> 获得的元素组成的 view views::keys 选取仿 pair 值组成的 view 并产生每个 pair 的第一元素的 view views::values 选取仿 pair 值组成的 view 并产生每个 pair 的第二元素的 view

Ranges采用了C++ 20的最新特性Concepts,并且是惰性执行的。
下面是常见的range类型:

概念 描述 std::ranges::input_range 可以从头到尾重复至少一次(

比如 std::forward_list, std::list, std::deque, std::array std::ranges::forward_range 可以从头到尾重复多次(

比如 std::forward_list, std::list, std::deque, std::array std::ranges::bidirectional_range 迭代器还可以向后移动(

比如 std::list, std::deque, std::array std::ranges::random_access_range 可以恒定时间跳转到元素(

比如 std::deque, std::array std::ranges::contiguous_range 元素总是连续存储在内存中(

比如 std::array

void Test() {
    using namespace std::ranges;

    std::string content{ "Hello! Welcome to learn new feature of C++ 20" };
    for (auto word : content | views::split(' ')) {
        std::cout << "-> ";
        for (char iter : word |
             views::transform([](char val) { return std::toupper(val); }))
            std::cout << iter;
    }
    std::cout << std::endl;
    // -> HELLO!-> WELCOME-> TO-> LEARN-> NEW-> FEATURE-> OF-> C++-> 20

    std::vector<int> vet{ 0, 45, 15, 100, 0, 0, 11, 48, 0, 3, 99, 4, 0, 0, 0, 1485 , 418, 116, 0 };
    std::vector<int> pat{ 0, 0 };
    for (auto part : vet | views::split(pat)) {
        std::cout << "-> ";
        for (int iter : part)
            std::cout << iter << ' ';
    }
    std::cout << std::endl;
    // -> 0 45 15 100 -> 11 48 0 3 99 4 -> 0 1485 418 116 0

    std::vector<std::string> data{ "Hello!", " Welcome to", " Learn"," C++ 20" };
    for (char iter : data
         | views::join     // &#x6CE8;&#x610F;,join&#x4E0D;&#x9700;&#x8981;&#x6DFB;&#x52A0;()
         | views::transform([](char val) { return std::tolower(val); })) {
        std::cout << iter;
    }
    std::cout << std::endl;
    // hello! welcome to learn c++ 20
}

void Test1() {
    using namespace std::ranges;
    std::string str{ "Hello! Welcome to learn new feature of C++ 20" };

    // &#x81EA;&#x5B9A;&#x4E49;&#x8303;&#x56F4;&#x9002;&#x914D;&#x5668;
    auto newAdaptor =
        views::transform([](char val) { return std::toupper(val); })
        | views::filter([](char val) { return !std::isspace(val); });

    for (char iter : str | newAdaptor) {
        std::cout << iter;
    }
    // HELLO!WELCOMETOLEARNNEWFEATUREOFC++20
}

int main(int argc, char* argv[]) {
    Test();
    std::cout << std::endl;
    Test1();
    return 0;
}
</std::string></int></int>
void Test1() {
    std::vector<int> vec{ 15,18,50,2,99,14,8,33,84,78 };

    // before C++20
    //std::sort(vec.begin(), vec.end(), std::greater());
    //std::sort(vec.begin() + 2, vec.end(), std::greater());

    // C++20
    // &#x5168;&#x6392;&#x5E8F;
    std::ranges::sort(vec, std::ranges::less());
    // &#x4EC5;&#x5BF9;&#x7B2C;&#x4E8C;&#x4E2A;&#x5143;&#x7D20;&#x4E4B;&#x540E;&#x7684;&#x6240;&#x6709;&#x5143;&#x7D20;&#x8FDB;&#x884C;&#x6392;&#x5E8F;
    //std::ranges::sort(std::views::drop(vec, 2), std::ranges::greater());
    // &#x53CD;&#x5411;&#x6392;&#x5E8F;
    std::ranges::sort(std::views::reverse(vec))

    for (auto& iter : vec) {
        std::cout << iter << " ";
    }
}

struct  Data {
    std::string name;
    std::string addr;
    // &#x5347;&#x5E8F;&#x6392;&#x5E8F;
    bool operator <(const data& other)const { return name < other.name; } 降序排序 bool operator>(const Data& other)const {
        return name > other.name;
    };
};

void Test2() {
    std::vector<data> strVet;
    strVet.emplace_back(Data{ "Jason","Jason house" });
    strVet.emplace_back(Data{ "Lily","Lily house" });
    strVet.emplace_back(Data{ "Mark","Mark house" });

    std::ranges::sort(strVet, std::less<data>());

    for (auto& iter : strVet) {
        std::cout << iter.name << " ";
    }
}

int main(int argc, char* argv[]) {
    Test1();
    Test2();
    return 0;
}
</data></data></(const></int>

Ranges & Views 框架的实践

题目:对1-100求平方和,筛选出前5个能被4整除的数值。
思路步骤:
1)将1-100存放在std::vector容器中;
2)对容器中的每个数值求平方和;
3)筛选出所有能被4整除的数值;
4)输出前5个。

实现:

// before C++20
void Test1() {
    // &#x7B5B;&#x9009;N&#x4E2A;
    constexpr unsigned num = 5;
    std::vector<int> vet(100);
    std::vector<int> newVet;
    // &#x5347;&#x5E8F;&#x521D;&#x59CB;&#x5316;
    std::iota(vet.begin(), vet.end(), 1);

    std::transform(vet.begin(), vet.end(), vet.begin(),
                   [](int val) { return val * val; }
    );
    std::copy_if(vet.begin(), vet.end(), std::back_inserter(newVet),
                 [](int val) { return val % 4 == 0; }
    );

    for (unsigned i = 0; i < num; i++) {
        std::cout << newVet[i] << ' ';
    }
    // 4 16 36 64 100
}

// C++20 &#x666E;&#x901A;&#x7248;
void Test2() {
    constexpr unsigned num = 5;
    std::vector<int> vec(100);
    std::iota(vec.begin(), vec.end(), 1);

    auto even = [](const int& a) {
        return a % 4 == 0;
    };

    auto square = [](const int& a) {return a * a; };

    for (auto iter : std::views::take(std::views::filter(std::views::transform(vec, square), even), num)) {
        std::cout << iter << ' ';
    }
    // 4 16 36 64 100
}

// C++20 &#x8FDB;&#x9636;&#x7248;
void Test3() {
    using namespace std::ranges;
    constexpr unsigned num = 5;

    for (auto iter : views::iota(1)
         | views::transform([](int val) { return val * val; })
         | views::filter([](int val) { return val % 4 == 0; })
         | views::take(num)) {
        std::cout << iter << ' ';
    }
    // 4 16 36 64 100
}

int main(int argc, char* argv[]) {
    Test1();
    std::cout << std::endl;
    Test2();
    std::cout << std::endl;
    Test3();
    return 0;
}
</int></int></int>

1)允许[=, this]作为Lambda捕获,并弃用此隐式捕获[=];
2)Lambda init-capture 中的包扩展:…args = std::move(args)](){};
3)static, thread_local, 和 Lambda 捕获结构化绑定;
4)模板形式 Lambda。

// Before C++20 &#x83B7;&#x53D6; vector &#x5143;&#x7D20;&#x7C7B;&#x578B;
auto func = [](auto vec){
    using T = typename decltype(vec)::value_type;
}

// C++20
auto func = []<typename t>(vector<t> vec){
    // ...

}
</t></typename>
// Before C++20
template<class f, class... args>
auto delay_invoke(F f, Args... args){
    return [f, args...]{
        return std::invoke(f, args...);
    }
}

// C++20
template<class f, class... args>
auto delay_invoke(F f, Args... args){
    // Pack Expansion:  args = std::move(args)...

    return [f = std::move(f), args = std::move(args)...](){
        return std::invoke(f, args...);
    }
}
</class></class>

智能指针(shared_ptr)线程安全吗?
是: 引用计数控制单元线程安全, 保证对象只被释放一次
否: 对于数据的读写没有线程安全

如何将智能指针变成线程安全?
1)使用 mutex 控制智能指针的访问
2)使用全局非成员原子操作函数访问, 诸如: std::atomic_load(), atomic_store(), …

缺点: 容易出错, 开发过程中容易遗漏添加这些操作。

C++20提供了原子智能指针,比如:atomic

详情请参见:

例子:

template<typename t>
class concurrent_stack {
    struct Node {
        T t;
        shared_ptr<node> next;
    };
    atomic_shared_ptr<node> head;
    // C++11: &#x53BB;&#x6389; "atomic_" &#x5E76;&#x4E14;&#x5728;&#x8BBF;&#x95EE;&#x65F6;, &#x9700;&#x8981;&#x7528;
    // &#x7279;&#x6B8A;&#x7684;&#x51FD;&#x6570;&#x63A7;&#x5236;&#x7EBF;&#x7A0B;&#x5B89;&#x5168;, &#x4F8B;&#x5982;&#x7528;std::tomic_load
public:
    class reference {
        shared_ptr<node> p;
        <snip>
    };
    auto find(T t) const {
        auto p = head.load(); // C++11: atomic_load(&head)
        while (p && p->t != t)
            p = p->next;
        return reference(move(p));
    }
    auto front() const {
        return reference(head);
    }
    void push_front(T t) {
        auto p = make_shared<node>();
        p->t = t; p->next = head;
        while (!head.compare_exchange_weak(p->next, p)){
    } // C++11: atomic_compare_exchange_weak(&head, &p->next, p); }
    void pop_front() {
        auto p = head.load();
        while (p && !head.compare_exchange_weak(p, p->next)) {
        } // C++11: atomic_compare_exchange_weak(&head, &p, p->next);
    }
};
</node></snip></node></node></node></typename>

上述例子来自 Herb Sutter 的 N4162 论文

std::jthread对象包含std::thread一个成员,提供完全相同的公共函数,这些函数只是向下传递调用。这使我们可以将任何内容更改std::thread为std::jthread,确保它将像以前一样工作。

自动合流(Joining)

C++20 在线程thread中新增了 std::jthread
功能:
1)支持中断;
2)析构函数中自动调用 join();
3)析构函数调用 stop_source.request_stop() 然后 join()。

例子:

// Before C++20
void Test() {
    std::thread  th;
    {
        th = std::thread([]() {
            for (unsigned i = 1; i < 10; ++i) {
                std::cout << i << " ";
                Sleep(500);
            }
                         });
    }

    // &#x5982;&#x679C;&#x6CA1;&#x6709;join()&#xFF0C;&#x76F4;&#x63A5;&#x9000;&#x51FA;&#x5C31;&#x4F1A;&#x5F15;&#x53D1;&#x5D29;&#x6E83;
    // th.join();
}

// C++20
void Test1() {
    std::jthread  th;
    {
        th = std::jthread([]() {
            for (unsigned i = 1; i < 10; ++i) {
                std::cout << i << " ";
                Sleep(500);
            }
                          });
    }

    // &#x6CA1;&#x6709;&#x4F7F;&#x7528;join&#x4E5F;&#x4E0D;&#x4F1A;&#x5D29;&#x6E83;,&#x56E0;&#x4E3A;std::jthread&#x7684;&#x6790;&#x6784;&#x51FD;&#x6570;&#x9ED8;&#x8BA4;&#x8C03;&#x7528;join()
}

int main(int argc, char* argv[]) {
    //Test();
    std::cout << std::endl;
    Test1();
    return 0;
}

可协作的中断(Cancellable)

在上述例子中使用的[ for (unsigned i = 1; i < 10; ++i) ],循环是10次;如果替换为while(1)时,整个函数就会被阻塞,阻塞在join()。因此线程没有执行结束并正常退出,此时函数join()就会一直等待下去。
在C++20中提供了可协作的中断操作,可以通过外部发起的请求,最后由线程内部决定是否中断并退出。

std::stop_token
用来查询线程是否中断,可以和condition_variable_any配合使用

std::stop_source
用来请求线程停止运行,stop_resources 和 stop_tokens 都可以查询到停止请求

std::stop_callback
如果对应的stop_token 被要求终止, 将会触发回调函数。
用法: std::stop_callback StopTokenCallback(OnStopToken, []{ // });

例子:

void Test3() {
    std::jthread  th;
    {
        th = std::jthread([]() {
            while (1) {
                std::cout << "1";
                Sleep(500);
            }
                          });
    }

    // &#x5916;&#x90E8;&#x53D1;&#x8D77;&#x4E2D;&#x65AD;&#x8BF7;&#x6C42;&#xFF0C;&#x4F46;&#x662F;&#x7EBF;&#x7A0B;&#x5185;&#x90E8;&#x6CA1;&#x6709;&#x54CD;&#x5E94;&#xFF0C;&#x4ECD;&#x7136;&#x4F1A;&#x963B;&#x585E;
    th.request_stop();

    // &#x6B64;&#x53E5;&#x6267;&#x884C;&#x4E86;&#xFF0C;&#x4F46;&#x662F;&#x6574;&#x4E2A;&#x51FD;&#x6570;&#x9000;&#x51FA;&#x65F6;&#x4ECD;&#x4F1A;&#x963B;&#x585E;
    std::cout << "Finish Test3.";
}

void Test4() {
    std::jthread  th;
    {
        th = std::jthread([](const std::stop_token st) {
            while (!st.stop_requested()) {
                // &#x6CA1;&#x6709;&#x6536;&#x5230;&#x4E2D;&#x65AD;&#x8BF7;&#x6C42;&#xFF0C;&#x5219;&#x6267;&#x884C;
                std::cout << "1";
                Sleep(500);
            }
                          });
    }

    Sleep(10 * 1000);

    // &#x5916;&#x90E8;&#x53D1;&#x8D77;&#x4E2D;&#x65AD;&#x8BF7;&#x6C42;
    auto ret = th.request_stop();
}

int main(int argc, char* argv[]) {
    //Test3();
    //std::cout << std::endl;

    Test4();
    std::cout << std::endl;

    return 0;
}

C++20之前,封装好的对象(比如类对象或结构体对象)若出现比较或排序的情况,就需要重载某个特定的运算符,有时候还需要多个不同的运算符重载。
C++20,提供了三路比较运算符,会默认生成一系列的比较运算符。 生成的默认运算符有六个即:==、!=、

详情说明请参见:

例子:
简单来说,对比于双目运算符(:?),多了一处相等比较的返回。

&#x53CC;&#x76EE;&#x8FD0;&#x7B97;&#x7B26;&#xFF1A;
a >= b ? b : a

&#x4E09;&#x8DEF;&#x8FD0;&#x7B97;&#x7B26;&#x8BED;&#x6CD5;&#xFF1A;
(a <=> b) < 0   // &#x5982;&#x679C; a < b &#x5219;&#x4E3A; true
(a <=> b) > 0   // &#x5982;&#x679C; a > b &#x5219;&#x4E3A; true
(a <=> b) == 0  // &#x5982;&#x679C; a &#x4E0E; b &#x76F8;&#x7B49;&#x6216;&#x8005;&#x7B49;&#x4EF7;&#xFF0C;&#x5219;&#x4E3A; true

&#x4E09;&#x8DEF;&#x8FD0;&#x7B97;&#x7B26;&#x5C55;&#x5F00;&#xFF1A;
auto res = a <=> b;
if (res < 0)
    std::cout << "a &#x5C0F;&#x4E8E; b";
else if (res > 0)
    std::cout << "a &#x5927;&#x4E8E; b";
else
    std::cout << "a &#x4E0E; b &#x76F8;&#x7B49;";
// &#x7C7B;&#x4F3C;&#x4E8E;C&#x7684;strcmp &#x51FD;&#x6570;&#x8FD4;&#x56DE;-1, 0, 1
</=></=></=></=>

在C++20之前,在map中以结构信息作为Key,必须提供一个排序的仿函数。如下例子:

struct UserInfo {
    std::string name;
    std::string addr;
};

struct Compare {
    bool operator()(const UserInfo& left, const UserInfo& right) const {
        return  left.name > right.name;
    }
};

int main(int argc, char* argv[]) {
    std::map <userinfo, bool, compare> infoMap;

    UserInfo usr1{ "Jason","Jason1111" };
    UserInfo usr2{ "Lily","Lily2222" };
    UserInfo usr3{ "Mark","Mark3333" };

    infoMap.insert(std::pair<userinfo, bool>(usr2, true));
    infoMap.insert(std::pair<userinfo, bool>(usr1, false));
    infoMap.insert(std::pair<userinfo, bool>(usr3, true));

    for (auto& iter : infoMap) {
        std::cout << iter.first.name << std::endl;
    }
    return 0;
}
</userinfo,></userinfo,></userinfo,></userinfo,>

在C++20中,可以直接使用默认的三路比较运算符。若某运算符不满足,亦可自定义功能。如下:

struct UserInfo {
    std::string name;
    std::string addr;

    // &#x9ED8;&#x8BA4;&#x5347;&#x5E8F;
    //std::strong_ordering operator<=>(const UserInfo&) const = default;

    // &#x81EA;&#x5B9A;&#x4E49;&#x4E0D;&#x540C;&#x7684;&#x6392;&#x5E8F;--&#x964D;&#x5E8F;
    std::strong_ordering operator<=>(const UserInfo& info) const {
        auto ret = name <=> info.name;
        return ret > 0 ? std::strong_ordering::less
            : (ret == 0 ? std::strong_ordering::equal : std::strong_ordering::greater);
    };
};

int main(int argc, char* argv[]) {
    std::map <userinfo, bool> infoMap;

    UserInfo usr1{ "Jason","Jason1111" };
    UserInfo usr2{ "Lily","Lily2222" };
    UserInfo usr3{ "Mark","Mark3333" };

    infoMap.insert(std::pair<userinfo, bool>(usr2, true));
    infoMap.insert(std::pair<userinfo, bool>(usr1, false));
    infoMap.insert(std::pair<userinfo, bool>(usr3, true));

    for (auto& iter : infoMap) {
        std::cout << iter.first.name << std::endl;
    }
    return 0;
}
</userinfo,></userinfo,></userinfo,></userinfo,></=></=></=>

Calendar

// creating a year
auto y1 = year{ 2021 };
auto y2 = 2021y;

// creating a mouth
auto m1 = month{ 9 };
auto m2 = September;

// creating a day
auto d1 = day{ 24 };
auto d2 = 24d;

weeks w{ 1 }; // 1 &#x5468;
days d{ w };  // &#x5C06; 1 &#x5468;&#x8F6C;&#x6362;&#x6210;&#x5929;&#x6570;
std::cout << d.count();

hours h{ d };  // &#x5C06; 1 &#x5468;&#x8F6C;&#x6362;&#x6210;&#x5C0F;&#x65F6;
std::cout << h.count();

minutes m{ w }; // &#x5C06; 1 &#x5468;&#x8F6C;&#x6362;&#x6210;&#x5206;&#x949F;
std::cout << m.count();
struct DaysAttr {
    sys_days sd;
    sys_days firstDayOfYear;
    sys_days lastDayOfYear;
    year y;
    month m;
    day d;
    weekday wd;
};

DaysAttr GetCurrentDaysAttr() {
    // &#x76EE;&#x7684;&#x83B7;&#x53D6;&#x4ECA;&#x5E74;&#x7684;&#x7B2C;&#x4E00;&#x5929;&#x548C;&#x6700;&#x540E;&#x4E00;&#x5929;&#xFF0C;&#x7EDF;&#x4E00;&#x521D;&#x59CB;&#x5316;

    DaysAttr attr;
    attr.sd = floor<days>(system_clock::now());
    year_month_day ymd = attr.sd;
    attr.y = ymd.year();
    attr.m = ymd.month();
    attr.d = ymd.day();
    attr.wd = attr.sd;
    attr.firstDayOfYear = attr.y / 1 / 1;
    attr.lastDayOfYear = attr.y / 12 / 31;

    return attr;
}

// &#x4E00;&#x5E74;&#x4E2D;&#x8FC7;&#x53BB;&#x7684;&#x5929;&#x6570;&#xFF0C;&#x4EE5;&#x53CA;&#x4E00;&#x5E74;&#x4E2D;&#x5269;&#x4F59;&#x7684;&#x5929;&#x6570;
void OverDaysOfYear() {
    // &#x8FD9;&#x4F1A;&#x6253;&#x5370;&#x51FA;&#x4E00;&#x5E74;&#x4E2D;&#x7684;&#x5929;&#x6570;&#xFF0C;&#x5176;&#x4E2D;1&#x6708;1&#x65E5;&#x4E3A;&#x7B2C;1&#x5929;&#xFF0C;&#x7136;&#x540E;&#x8FD8;&#x4F1A;&#x6253;&#x5370;&#x51FA;&#x8BE5;&#x5E74;&#x4E2D;&#x5269;&#x4F59;&#x7684;&#x5929;&#x6570;&#xFF08;&#x4E0D;&#x5305;&#x62EC;&#xFF09;sd&#x3002;&#x6267;&#x884C;&#x6B64;&#x64CD;&#x4F5C;&#x7684;&#x8BA1;&#x7B97;&#x91CF;&#x5F88;&#x5C0F;&#x3002;
    // &#x5C06;&#x6BCF;&#x4E2A;&#x7ED3;&#x679C;&#x9664;&#x4EE5;days{1}&#x4E00;&#x79CD;&#x65B9;&#x6CD5;&#x53EF;&#x4EE5;&#x63D0;&#x53D6;&#x6574;&#x6574;&#x7C7B;&#x578B;&#x4E2D;&#x7684;&#x5929;&#x6570;dn&#x5E76;&#x5C06;&#x5176;dl&#x5206;&#x6210;&#x6574;&#x6570;&#xFF0C;&#x4EE5;&#x8FDB;&#x884C;&#x683C;&#x5F0F;&#x5316;&#x3002;

    auto arrt = GetCurrentDaysAttr();
    auto dn = arrt.sd - arrt.firstDayOfYear + days{ 1 };
    auto dl = arrt.lastDayOfYear - arrt.sd;
    std::cout << "It is day number " << dn / days{ 1 } << " of the year, "
        << dl / days{ 1 } << " days left." << std::endl;
}

// &#x8BE5;&#x5DE5;&#x4F5C;&#x65E5;&#x6570;&#x548C;&#x4E00;&#x5E74;&#x4E2D;&#x7684;&#x5DE5;&#x4F5C;&#x65E5;&#x603B;&#x6570;
void WorkDaysOfYear() {
    // wd&#x662F;|attr.wd = attr.sd|&#x8BA1;&#x7B97;&#x7684;&#x661F;&#x671F;&#x51E0;&#xFF08;&#x661F;&#x671F;&#x4E00;&#x81F3;&#x661F;&#x671F;&#x65E5;&#xFF09;&#x3002;
    // &#x8981;&#x6267;&#x884C;&#x8FD9;&#x4E2A;&#x8BA1;&#x7B97;&#xFF0C;&#x6211;&#x4EEC;&#x9996;&#x5148;&#x9700;&#x8981;&#x7684;&#x7B2C;&#x4E00;&#x4E2A;&#x548C;&#x6700;&#x540E;&#x4E00;&#x4E2A;&#x65E5;&#x671F;wd&#x7684;&#x5F53;&#x5E74;y&#x3002;|arrt.y / 1 / arrt.wd[1]|&#x662F;wd&#x4E00;&#x6708;&#x7684;&#x7B2C;&#x4E00;&#x4E2A;&#xFF0C;|arrt.y / 12 / arrt.wd[last]|&#x5219;&#x662F;wd&#x5341;&#x4E8C;&#x6708;&#x7684;&#x6700;&#x540E;&#x4E00;&#x4E2A;&#x3002;
    // wd&#x4E00;&#x5E74;&#x4E2D;&#x7684;&#x603B;&#x6570;&#x4EC5;&#x662F;&#x8FD9;&#x4E24;&#x4E2A;&#x65E5;&#x671F;&#x4E4B;&#x95F4;&#x7684;&#x5468;&#x6570;&#xFF08;&#x52A0;1&#xFF09;&#x3002;&#x5B50;&#x8868;&#x8FBE;&#x5F0F;[lastWd - firstWd]&#x662F;&#x4E24;&#x4E2A;&#x65E5;&#x671F;&#x4E4B;&#x95F4;&#x7684;&#x5929;&#x6570;&#x3002;&#x5C06;&#x8BE5;&#x7ED3;&#x679C;&#x9664;&#x4EE5;1&#x5468;&#x5C06;&#x5F97;&#x5230;&#x4E00;&#x4E2A;&#x6574;&#x6570;&#x7C7B;&#x578B;&#xFF0C;&#x8BE5;&#x6574;&#x6570;&#x7C7B;&#x578B;&#x4FDD;&#x5B58;&#x4E24;&#x4E2A;&#x65E5;&#x671F;&#x4E4B;&#x95F4;&#x7684;&#x5468;&#x6570;&#x3002;
    // &#x661F;&#x671F;&#x6570;&#x7684;&#x8BA1;&#x7B97;&#x65B9;&#x6CD5;&#x4E0E;&#x661F;&#x671F;&#x603B;&#x6570;&#x7684;&#x8BA1;&#x7B97;&#x65B9;&#x6CD5;&#x76F8;&#x540C;&#xFF0C;&#x4E0D;&#x540C;&#x7684;&#x662F;&#x661F;&#x671F;&#x6570;&#x4ECE;&#x5F53;&#x5929;&#x5F00;&#x59CB;&#x800C;&#x4E0D;&#x662F;wd&#x4E00;&#x5E74;&#x7684;&#x6700;&#x540E;&#x4E00;&#x5929;&#x5F00;&#x59CB;|sd - firstWd|&#x3002;

    auto arrt = GetCurrentDaysAttr();
    sys_days firstWd = arrt.y / 1 / arrt.wd[1];
    sys_days lastWd = arrt.y / 12 / arrt.wd[last];
    auto totalWd = (lastWd - firstWd) / weeks{ 1 } + 1;
    auto n_wd = (arrt.sd - firstWd) / weeks{ 1 } + 1;
    std::cout << format("It is {:%A} number ", arrt.wd) << n_wd << " out of "
        << totalWd << format(" in {:%Y}.}", arrt.y) << std::endl;;
}

// &#x8BE5;&#x5DE5;&#x4F5C;&#x65E5;&#x6570;&#x548C;&#x4E00;&#x4E2A;&#x6708;&#x4E2D;&#x7684;&#x5DE5;&#x4F5C;&#x65E5;&#x603B;&#x6570;
void WorkDaysAndMonthOfYear() {
    // &#x4ECE;wd&#x5E74;&#x6708;&#x5BF9;&#x7684;&#x7B2C;&#x4E00;&#x4E2A;&#x548C;&#x6700;&#x540E;&#x4E00;&#x4E2A;&#x5F00;&#x59CB;|arrt.y / arrt.m|,&#x800C;&#x4E0D;&#x662F;&#x6574;&#x4E2A;&#x5168;&#x5E74;&#x5F00;&#x59CB;

    auto arrt = GetCurrentDaysAttr();
    sys_days firstWd = arrt.y / arrt.m / arrt.wd[1];
    sys_days lastWd = arrt.y / arrt.m / arrt.wd[last];
    auto totalWd = (lastWd - firstWd) / weeks{ 1 } + 1;
    auto numWd = (arrt.sd - firstWd) / weeks{ 1 } + 1;
    std::cout << format("It is {:%A} number }", arrt.wd) << numWd << " out of "
        << totalWd << format(" in {:%B %Y}.", arrt.y / arrt.m) << std::endl;;
}

// &#x4E00;&#x5E74;&#x4E2D;&#x7684;&#x5929;&#x6570;
void DaysOfYear() {
    auto arrt = GetCurrentDaysAttr();
    auto total_days = arrt.lastDayOfYear - arrt.firstDayOfYear + days{ 1 };
    std::cout << format("Year {:%Y} has ", y) << total_days / days{ 1 } << " days." << std::endl;;
}

// &#x4E00;&#x4E2A;&#x6708;&#x4E2D;&#x7684;&#x5929;&#x6570;
void DaysOfMonth() {
    // &#x8868;&#x8FBE;&#x5F0F;|arrt.y / arrt.m / last|&#x662F;&#x5E74;&#x4EFD;-&#x6708;&#x4EFD;&#x5BF9;&#x7684;&#x6700;&#x540E;&#x4E00;&#x5929;,|arrt.y / arrt.m|&#x5C31;&#x662F;|arrt.y / arrt.m / 1|&#x6708;&#x4EFD;&#x7684;&#x7B2C;&#x4E00;&#x5929;&#x3002;
    // &#x4E24;&#x8005;&#x90FD;&#x8F6C;&#x6362;&#x4E3A;sys_days&#xFF0C;&#x56E0;&#x6B64;&#x53EF;&#x4EE5;&#x51CF;&#x53BB;&#x5B83;&#x4EEC;&#x4EE5;&#x5F97;&#x5230;&#x5B83;&#x4EEC;&#x4E4B;&#x95F4;&#x7684;&#x5929;&#x6570;&#x3002;&#x4ECE;1&#x5F00;&#x59CB;&#x7684;&#x8BA1;&#x6570;&#x52A0;1&#x3002;

    auto arrt = GetCurrentDaysAttr();
    auto totalDay = sys_days{ arrt.y / arrt.m / last } - sys_days{ arrt.y / arrt.m / 1 } + days{ 1 };
    std::cout << format("{:%B %Y} has ", arrt.y / arrt.m) << totalDay / days{ 1 } << " days." << std::endl;;
}
</days>

对于部分不喜欢”常规语法”的开发者,可以使用完整的”构造函数语法”来代替。

&#x4F8B;&#x5982;&#xFF1A;
sys_days newYear = y/1/1;
sys_days firstWd = y/1/wd[1];
sys_days lastWd = y/12/wd[last];

&#x53EF;&#x4EE5;&#x66FF;&#x6362;&#x4E3A;&#xFF1A;
sys_days newYear = year_month_day{y, month{1}, day{1}};
sys_days firstWd = year_month_weekday{y, month{1}, weekday_indexed{wd, 1}};
sys_days lastWd = year_month_weekday_last{y, month{12}, weekday_last{wd}};

Timezone

time_zone表示特定地理区域的所有时区转换。
C++语言标准记得选择:/std:c++latest

例子:

int main()
{
    constexpr std::string_view locations[] = {
        "Africa/Casablanca",   "America/Argentina/Buenos_Aires",
        "America/Barbados",    "America/Indiana/Petersburg",
        "America/Tarasco_Bar", "Antarctica/Casey",
        "Antarctica/Vostok",   "Asia/Magadan",
        "Asia/Manila",         "Asia/Shanghai",
        "Asia/Tokyo",          "Atlantic/Bermuda",
        "Australia/Darwin",    "Europe/Isle_of_Man",
        "Europe/Laputa",       "Indian/Christmas",
        "Indian/Cocos",        "Pacific/Galapagos",
    };
    constexpr auto width = std::ranges::max_element(locations, {},
        [](const auto& s) { return s.length(); })->length();

    for (const auto location : locations) {
        try {
            // may throw if location is not in the time zone database
            const std::chrono::zoned_time zt{location, std::chrono::system_clock::now()};
            std::cout << std::setw(width) << location << " - Zoned Time: " << zt << '\n';
        } catch (std::chrono::nonexistent_local_time& ex) {
            std::cout << "Error: " << ex.what() << '\n';
        }
    }
}

constexpr

既能参与函数的声明,又能参与变量的声明
constexpr可用于编译或运行时函数,它的结果是常量。
constexpr的主要作用是声明变量的值或函数的返回值可以在常量表达式(即编译期便可计算出值的表达式)中使用。

int Func() {
    return 1;
}

// &#x53EF;&#x4FEE;&#x6539;&#x4E3A;
//constexpr int Func() {
//    return 1;
//}

constexpr const int x = 5;  // OK
constexpr const int y = Func(); // Error

consteval

只能参与函数的声明
当某个函数使用consteval声明后,则所有带有求值的操作,来调用这个函数的表达式时,必须为常量表达式。
实际上是编译时运行的函数,也就是它的参数在编译时是”确定的”(常量),它的结果也是常量。

例子:

consteval int Test1(int val) {
    return ++val;
}
constexpr int Test2(int val) {
    return ++val;
}

int main(int argc, char* argv[]) {
    int ret = Test1(10);
    std::cout << ret << std::endl;
    //int val = Test1(ret);   //error , ret is not const
    int val = Test2(ret);
    std::cout << val;
    return 0;
}

如上例子所示:
ret是函数返回的变量,而由consteval定义的函数必须在编译时可运行出常量结果,因此冲突了。int val = Test1(ret)无法被调用。
constexpr既可在编译时也是可以在运行时,因此可以接受变量参数。

constinit

只能参与变量的声明
constinit只能用于static或thread_local,不能与constexpr、consteval一起使用。
constinit的作用在于显式地指定变量的初始化方式为静态初始化。
其生命周期必须为 静态生命周期或线程本地(Thread-local)生命周期(即不能为局部变量),其初始化表达式必须是一个常量表达式。

constexpr的变量是const类型,只读,不能二次修改;constinit是说变量在程序开始时被初始化,是static类型,不能在运行时被创建,变量不要求是const类型,可以被二次修改。

例子:

consteval int Test1() {
    return 1;
}

int Test2() {
    return 2;
}

void Test3() {
    constinit int e = 20;  // Error: e is not static
}

constinit int a = 100;    // OK
constinit int b = Test1(); // OK&#xFF0C;run time
constinit thread_local int c = 200; // OK
constinit int d = Test2(); // Error: Test2() is not a constant expression

int Test4() {
    // constinit can be modified
    a += 200;   // run time
    b = 2000;
    c -= 50;
    return a;
}
enum class Color {
    kRed,
    kBlue,
    kGreen,
};

// before C++20
std::string_view Color2String(const Color color) {
    switch (color) {
    case Color::kRed:
        return "Red";
    case Color::kBlue:
        return "Blue";
    case Color::kGreen:
        return "Green";
    }
    return "Red";
}

// C++20
std::string_view Color2String(const Color color) {
    switch (color) {
        using enum Color;  // feature
    case kRed:
        return "Red";
    case kBlue:
        return "Blue";
    case kGreen:
        return "Green";
    }
    return "Red";
}

Original: https://www.cnblogs.com/gd-luojialin/p/15423391.html
Author: gd_沐辰
Title: C++20新特性

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/535426/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • C++STL之双端队列容器

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/huashanqingzhu/p/12832819.ht…

    C++ 2023年5月29日
    061
  • NDK自带的c/c++库

    1.官方文档 https://developer.android.google.cn/ndk/guides/stable_apis https://developer.androi…

    C++ 2023年5月29日
    065
  • c++实训课

    程序一: include 程序二: include 程序三: include Original: https://www.cnblogs.com/duanqibo/p/164138…

    C++ 2023年5月29日
    052
  • C++入门笔记

    一直对C++感到很恐惧,大学里有C的基础,今天终于鼓足勇气入门C++,先大致了解一下,以后用到的时候再详细深入。 Android中有一些很火的领域比如:音视频、物联网,都会涉及到J…

    C++ 2023年5月29日
    061
  • EclipseC++学习笔记-3 直接在wsl2中启动带界面应用

    1、下载https://sourceforge.net/projects/vcxsrv/2、安装运行 注意不要多次启动最后一步可以保存为快捷方式,下次直接双击启动3. 设置WSL …

    C++ 2023年5月29日
    066
  • C++解决share_ptr造成的循环引用

    参考链接:https://blog.csdn.net/yc2zgh1314/article/details/51264963 https://www.cnblogs.com/dua…

    C++ 2023年5月29日
    046
  • Floyd算法(二)之 C++详解

    和Dijkstra算法一样,弗洛伊德(Floyd)算法也是一种用于寻找给定的加权图中顶点间最短路径的算法。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授…

    C++ 2023年5月29日
    080
  • [C++] 引用

    引用的特点 通常意义上的引用是”左值引用”,(相对于右值引用,即 rvalue reference)。 引用是语法糖,变量别名。声明一个引用,不是新定义了一…

    C++ 2023年5月29日
    047
  • [C++] 对象指针使用方法

    对象指针:指向类对象的指针 类指针指向类变量(对象)的地址 对象指针定义格式: 举例: #include using namespace std; class Student { …

    C++ 2023年5月29日
    054
  • C/C++知识点记录

    1. strcpy 的部分理解char strcpy(char dest, const charsrc){while(dest++=src++)return dest-1;}这是s…

    C++ 2023年5月29日
    062
  • C++矩阵运算库推荐

    最近在几个地方都看到有人问C++下用什么矩阵运算库比较好,顺便做了个调查,做一些相关的推荐吧。主要针对稠密矩阵,有时间会再写一个稀疏矩阵的推荐。 Armadillo:C++下的Ma…

    C++ 2023年5月29日
    066
  • Dijkstra算法(二)之 C++详解

    迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。 基本…

    C++ 2023年5月29日
    045
  • 34.C++-QT信号槽分析

    moc 元对象编译器, 全称是 Meta-Object Compiler,也就是”元对象编译器”。是QT翻译扩展语法到C++语言的工具,目前见扩展了信号与槽…

    C++ 2023年5月29日
    055
  • !! vc C++ VS2017检查内存泄漏具体到某一行代码

    VLD工具可以用来检查VS C++程序的内存泄露。 VLD官网:https://kinddragon.github.io/vld/ 官网不方便下载的,可以用我的链接:https:/…

    C++ 2023年5月29日
    044
  • c++容器set

    class Solution { public: vector intersection(vector& nums1, vector& nums2) { // ve…

    C++ 2023年5月29日
    049
  • 使用Visual Leak Detector for Visual C++ 捕捉内存泄露

    1、下载vlc 2、创建应用并配置 c/c++ -> General -> Additional Include Directories = C:\Program Fi…

    C++ 2023年5月29日
    047
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球