Add keywords (keywords)

concept

requires

constinit

consteval

co_await

co_return

co_yield

char8_t

Modules (Modules)

Advantages: 1) No header files; 2) Declare that the implementation is still separable, but not necessary; 3) You can explicitly specify which classes or functions to export; 4) No need for header files to repeatedly include Guards; 5) Names of modules can be the same without conflict; 6) The module is only processed once, making compilation faster (header files need to be processed every time they are introduced); 7) Preprocessing macros are only valid within modules; 8) The introduction of modules has nothing to do with the order of introduction.

Example: 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: the return of 12.257902012398877; Case kDouble: the return of 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 /////////////////////////////////////////////Copy the code

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;
}
Copy the code

Summary 1) Module name optional format: xxx.yyy.zzz 2) In the MSVC compiler, module file is.ixx, not.cpp. (the. Ixx file suffix is the default module interface in the MSVC compiler, C++ module interface unit) Otherwise, it cannot be called externally. You can also choose to export namespace blocks. See the examples above for details. 4) The functions provided by the standard library core and filesystem are not recognized. It may be that I import module configuration operation error! 5) The module currently supports basic data types, such as bool, int, float, char, etc. The use of structures and classes is also supported. 6) There is no header file for module definition, no repeated compilation of output, and no difference in the order of module import. Therefore, it is recommended to add corresponding namespace for module compilation to reduce the possibility of the same symbol.

See:

The beauty of configuration generalization for module in C++20 module VS2019 — the beauty of variable template parameters in C++11

Coroutines (Coroutines)

Process: The basic unit of operating system resource allocation. Scheduling involves switching between user space and kernel space, which consumes a lot of resources. Threads: The basic unit of operation of an operating system. Under the framework of the same process resource, preemptive multi-task is realized, relative to the process, and the resource consumption of execution unit switching is reduced. Coroutines: Very similar to threads. However, the idea of cooperative multi-task is changed to realize cooperative scheduling by users, that is, voluntarily surrender control (or called user mode thread).

What exactly is a coroutine?

In simple terms, a coroutine is a special function that suspends execution at some point, returns to the caller or restorer (possibly with a return value), and allows subsequent execution to resume from where it was suspended. Note that this does not mean suspending execution of the function thread, but simply suspending execution of the function itself. In plain English, the use is to “synchronize” “asynchronous” style programming.

C++20 coroutine features

1) No internal stack allocation is required, only a top frame of the call stack is required. 2) During coroutine operation, keywords need to be used to control the operation process (such as CO_return). 3) Coroutines may allocate different threads to trigger resource competition. 4) No scheduler, but standard and compiler support is required.

The characteristic of coroutines is that they are executed by one thread. What are the advantages of coroutines compared to multithreading? Advantages: 1) Extremely high execution efficiency: because subroutine switching is not thread switching, but by the program itself control, therefore, there is no overhead of thread switching. The greater the number of threads, the greater the performance advantage of coroutines compared to multithreading. 2) Do not need multithreading locking mechanism: because there is only one thread, there is no conflict of variables written at the same time, in the coroutine control of shared resources without locking, only need to judge the state, so the execution efficiency is much higher than multithreading.

Disadvantages: 1) Unable to utilize multi-core resources: The nature of coroutines is single thread, it cannot use multiple cores of a single CPU at the same time, coroutines need to work with processes to run on multiple cpus. Of course, most of the applications we write on a daily basis don’t need this, unless they are CPU intensive. 2) Blocking the entire program when you do a Blocking operation (e.g. IO).

Keywords in coroutines

Co_wait: Suspends the coroutine and waits for other calculations to complete. Co_return: return from coroutine (coroutine return is prohibited); Co_yield: Returns a result and suspends the current coroutine. The next call continues the coroutine. Note: The coroutine keywords above can only be used in coroutines. This means calling co_await XXXX () directly from the main function; No.

How to define and use coroutines?

A few basic concepts: 1) There can only be one coroutine per thread; 2) The coroutine function needs to return a Promise; 3) All keywords of coroutines must be used in coroutine functions; 4) Asynchronous functions can be called synchronously in coroutine functions by wrapping the asynchronous functions in Awaitable classes and using the co_wait keyword.

Once you understand the above concepts, you can create and use coroutines according to specific rules: 1) call only one coroutine function in a thread at a time, that is, call another coroutine function after only one coroutine function has completed its execution; 2) Use the Awatiable class to wrap all asynchronous functions that handle part of a request (such as executing an SQL query, or executing an HTTP request, etc.); 3) Call these asynchronous functions synchronously by adding the co_WAIT keyword in the corresponding coroutine functions as needed. Note that an asynchronous function (the wrapped Awaiable class) can be called in more than one coroutine function. Coroutine functions can be called in more than one thread (although only one coroutine function is called in one thread at a time), so it is best to keep the Awaiable class thread-safe and avoid locking. 4) Respond to different requests by calling different coroutine functions in the thread.

Coroutines typically need to define three things: coroutine bodies, coroutines promising characteristic types, and await objects. C++20 coroutine template :(for reference only, unofficial standard)

#include <thread> #include <coroutine> #include <functional> #include <windows.h CoroutineTraits {/ / name custom | Test | struct promise_type {/ / name must be | promise_type | / / must implement this interface. (called when the coroutine body is created.) Auto get_return_object() {return CoroutineTraits{}; }; // This interface must be implemented and the return value must be of type Awaitable. Auto initial_suspend() {return STD ::suspend_never{}; //return STD ::suspend_always{}; Always indicates that the coroutine is suspended} // This interface must be implemented and the return value must be of type Awaitable. Auto final_suspend() noexcept {return STD ::suspend_never{}; } // This interface must be implemented to handle errors thrown inside coroutines void unhandled_exception() {STD ::terminate(); } // This interface must be implemented if there is no co_return keyword inside the coroutine function. Coroutines executive body (called) after void return_void () {} / / note: | return_void | and | return_value | are mutually exclusive. // This interface must be implemented if the coroutine function has the keyword co_return inside. //void return_value() {} // If the coroutine function has the keyword co_yield, this interface must be implemented. The returned value must be awaitable auto yield_value(int value) {// _valu=value; / / can do some save or other with value | | return STD: : suspend_always {}; }}; }; / / coroutines await object struct CoroutineAwaitObj {/ / name custom | CoroutineAwaitObj | / / await completion has been calculated, if return true, Bool await_ready() const {return false; } // await the result STD ::string await_resume() const {return _result; } // await where the object is actually called asynchronously, Void await_suspend(const STD ::coroutine_handle<> handle) {STD ::jthread([handle, const STD ::coroutine_handle<> handle); _result = "Test"; // Resume the coroutine handle.resume(); }).detach(); } // store the return value here STD ::string _result; }; / / / / coroutines body | CoroutineTraits | not return a value, but coroutines feature types; It cannot be a return type such as void or string. 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; }Copy the code

The execution flow of coroutines

Promise_type (); promise_type(); 2) With the promise object, get_return_object() is executed to generate a coroutine_name object and record handle; 3) Execute initial_suspend() and determine whether to execute the coroutine function immediately according to the return value of await_ready(). Execute the coroutine function immediately if the return value of await_ready() is true. Otherwise, await_suspend suspends the coroutine and jumps to the main function by calling the return value. We return STD ::suspend_always, whose await_ready() always returns false. 4) Call the coroutine function CoroutineFunc () in the main function and pass execution to the coroutine function; 5) Perform operations in coroutine functions until co_wait, CO_return, and co_yield are executed; 6) Execute the await_ready() function in awaitable class and decide whether to transfer execution authority to the main function based on the return value. If await_READY returns false, await_suspend() is called; If await_ready returns true, await_resume() is executed and returns to the main function (await_suspend() is not executed); 7) The coroutine function is ready to return because it has finished executing the statement. There is no co_return, so return_void() is called. If there is co_return, the return_value() function is called; 8) Final_suspend is then called, and the coroutine performs the closing action. The aWAIT_ready value of final_suspend is used to determine whether the promise object is destruct immediately. If true is returned, the promise object is destruct immediately. Otherwise, the promise object is not destruct immediately and the execution power is given to the main function. Note: If the promise object is destructed immediately, the subsequent main function cannot get the corresponding value through the Promise. 9) Return the main function to perform other operations, return 0.

Storage space and life cycle of coroutines

1) C++20 is designed to be stack-free, with all local state stored on the heap. 2) Allocate space for storing the state of the coroutine. When allocating a frame, first search for whether the state of the coroutine provides an operator new, and then search for the global scope. 3) There will also be storage allocation failures. For example, if you write get_return_object_on_allocation_failure(), that’s what happens after failure, instead of get_return_object() to do the job (noexcept can be added); 5) The storage space of the coroutine will be destructed only after final_suspend. Or manually call handle.destroy() explicitly. Otherwise the coroutine’s storage space will never be released; If you stop at final_suspend, you must manually call handle.destroy() inside the wrapper function or you will have a memory leak. 6) If final_suspend or handle.destroy() is completed, the storage space of the coroutine has been freed; Any further operation on Handle will result in a segment error.

Coroutine use cases refer to:

C++20 Coroutines are easy to understand

Ranges & Views

Ranges: Are abstractions of “collections of items” or “iterable things”. The most basic definition only needs to have begin() and end() in the scope. Range represents a list of elements, or a list of elements similar to begin/end pairs. View: see string_view for its meaning. This string_view is very cheap to copy and can be copied without passing a reference. Ranges by adding a technique called the view (view) concept, implements the Lazy Evaluation (inert Evaluation), and it can convert various relations of the view with the symbol “|”. Range adaptor: Converts a range to a view(and a view to another view). The range adapter takes viewable_range as its first argument and returns a view.

Common range adapters:

The adapter describe
views::filter A view consisting of the elements in range that satisfy a predicate
views::transform A view that applies some transformation function to each element of the sequence
views::take A view consisting of the first N elements of another view
views::join A view composed of a sequence of views obtained by flattening a range of views
views::elements The view of the NTH element of each tuple is generated by selecting the view composed of the value of the imitation tuple and the value N
views::drop A view consisting of another view skipping the first N elements
views::all A view that contains all the elements of a range
views::take_while A view consisting of the starting elements of another view until the first predicate returns false
views::drop_while A view consisting of another view that skips the starting sequence of elements until the element whose first predicate returns false
views::split View of a subrange obtained by cutting another view with a delimiter
views::common Convert the view to common_range
views::reverse A view that iterates through elements on another bidirectional view in reverse order
views::istream_view A view consisting of elements obtained by applying operator>> successively to the associated input stream
views::keys Select a view from the pair values and generate a view for the first element of each pair
views::values Select the view of the pair and generate the view of the second element of each pair

Ranges uses the latest C++ 20 feature, Concepts, and is lazily implemented. Here are the common range types:

concept describe
std::ranges::input_range You can repeat at least once from start to finish (A one-way range that can be traversed only once)

Such as STD ::forward_list, STD ::list, STD ::deque, STD ::array
std::ranges::forward_range Can be repeated from beginning to end many times (A one-way range that can be traversed multiple times)

Such as STD ::forward_list, STD ::list, STD ::deque, STD ::array
std::ranges::bidirectional_range Iterators can also be moved backwards (Two-way range)

Like STD :: List, STD ::deque, STD :: Array
std::ranges::random_access_range Can jump to elements in constant time (Range that supports random access)

Such as STD: : a deque, STD: : array
std::ranges::contiguous_range Elements are always stored consecutively in memory (A continuous range of contents)

Such as STD: : array

Simple practices for range adapters

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 / / note that the join don't need to add () | 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" }; 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; }Copy the code

Ranges order containers

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 / / full sorting STD: : ranges: : sort (vec, STD: : ranges: : less ()); // Just sort all elements after the second element // STD ::ranges::sort(STD ::views::drop(vec, 2), STD ::ranges::greater()); // Reverse order STD ::ranges::sort(STD ::views::reverse(vec)) for (auto& iter: vec) {STD ::cout << iter << ""; } } struct Data { std::string name; std::string addr; Bool operator <(const Data& other)const {return name < other.name; } // Descending sort 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; }Copy the code

The practice of Ranges & Views framework

Take the sum of squares from 1 to 100 and select the first five values divisible by 4. 1) Store 1-100 in STD ::vector; 2) Sum the squares of each value in the container; 3) Screen out all values divisible by 4; 4) Output the first 5.

Implementation:

// before C++20 void Test1() {// select N constexpr unsigned num = 5; std::vector<int> vet(100); std::vector<int> newVet; // Initialize 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] << ' '; } // C++20 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 上 版 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; }Copy the code

Update of Lambda expressions

1) Allow [=, this] to be captured as Lambda, and discard the implicit capture [=]; 2) Package extensions in Lambda init-Capture:… Args = STD: : move (args)] () {}; 3) Static, thread_local, and Lambda capture structured bindings; 4) Template form Lambda.

Lambda expressions in template form

Auto func = [](auto vec){using T = typename decltype(vec)::value_type; } // C++20 auto func = []<typename T>(vector<T> vec){ // ... }Copy the code

Lambda expression capture supports package expansion

// 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...) ; }}Copy the code

Atomic smart pointer

Is smart Pointer (shared_ptr) thread safe? Yes: Reference counting controls unit thread safety to ensure that objects are released only once No: Data reads and writes are not thread safe

How do I make smart Pointers thread safe? 1) Use mutex to control access to smart Pointers 2) use global nonmember atomic operation functions such as: STD ::atomic_load(), atomic_store()… Disadvantages: Error-prone, easy to forget to add these operations during development.

C++20 provides atomic intelligent Pointers, such as: atomic

, atomic

may use mutex for its internal principle; Global nonmember atomic manipulation functions are marked deprecated

For details, see:

Thread safety STD :: Atomic (STD ::shared_ptr)

Example:

template<typename T> class concurrent_stack { struct Node { T t; shared_ptr<Node> next; }; atomic_shared_ptr<Node> head; // C++11: remove "atomic_" and use special functions to control thread safety, such as 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); }};Copy the code

The above example comes from Herb Sutter’s N4162 paper

Automatic Joining, a thread that can be Cancellable

The STD :: JThread object contains a member of STD :: Thread and provides exactly the same public functions that are simply passed down to be called. This allows us to change anything from STD :: Thread to STD :: jThread, ensuring that it will work as before.

Automatic Joining

C++20 added STD :: jthreads to thread. Features: 1) Support interruption; 2) Join () is automatically called in destructor; 3) Destructor calls stop_source.request_stop() then join().

Example:

// Before C++20 void Test() { std::thread th; { th = std::thread([]() { for (unsigned i = 1; i < 10; ++i) { std::cout << i << " "; Sleep(500); }}); } // If there is no join(), exit will crash // th.join(); } // C++20 void Test1() { std::jthread th; { th = std::jthread([]() { for (unsigned i = 1; i < 10; ++i) { std::cout << i << " "; Sleep(500); }}); } int main(int argc, char* argv[]) {//Test(); std::cout << std::endl; Test1(); return 0; }Copy the code

Collaborative interruption (Cancellable)

In the example above [for (unsigned I = 1; I < 10; ++ I)], the loop is 10 times; If replaced with while(1), the whole function is blocked, blocking on join(). Therefore, the thread does not complete and exits normally, while the function join() waits forever. In C++20, collaborative interrupt operations are provided, which can be made through externally initiated requests, and finally decided internally by the thread whether to interrupt and exit.

Grammar specification

STD ::stop_token is used to query whether a thread is interrupted and can be used in conjunction with condition_variable_any

STD ::stop_source is used to request a thread to stop running. Stop_resources and stop_tokens can be queried for stop requests

STD ::stop_callback if the corresponding stop_token is asked to terminate, the callback function is triggered. STD ::stop_callback StopTokenCallback(OnStopToken, []{/*… * /});

Example:

void Test3() { std::jthread th; { th = std::jthread([]() { while (1) { std::cout << "1"; Sleep(500); }}); } // an external interrupt request will block th. Request_stop (); STD ::cout << "Finish Test3."; } void Test4() { std::jthread th; { th = std::jthread([](const std::stop_token st) { while (! St.stop_requested ()) {STD ::cout << "1"; Sleep(500); }}); } Sleep(10 * 1000); Auto ret = th.request_stop(); } int main(int argc, char* argv[]) { //Test3(); //std::cout << std::endl; Test4(); std::cout << std::endl; return 0; }Copy the code

Three-way comparison operator (<=>)

Prior to C++20, comparison or sorting of encapsulated objects (such as class or struct objects) required overloading of a particular operator, and sometimes multiple different operator overloads. C++20, which provides three-way comparison operators, generates a series of comparison operators by default. The generated default operators have six: ==,! =, <, >, <=, >=

For details, see:

Comparison operators compare by default

Example: In a nutshell, compare to the binocular operator (:?) , there is an equal comparison return.

Binocular operator: A >= b? b : A Three-way operator syntax: (a <=> b) < 0 // True if a < b (a <=> b) > 0 // True if a > b (a <=> b) == 0 // True if a and B are equal or equivalent:  auto res = a <=> b; If (res < 0) STD ::cout << "A < b"; Else if (res > 0) STD ::cout << "A > b"; Else STD ::cout << "A equals b "; // STRCMP similar to C returns -1, 0, 1Copy the code

Prior to C++20, you had to provide a sort functor with structural information as the Key in a map. Here’s an example:

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; }Copy the code

In C++20, you can use the default three-way comparison operator directly. If an operator does not meet the requirements, you can customize the function. As follows:

struct UserInfo { std::string name; std::string addr; // STD ::strong_ordering operator<=>(const UserInfo&) const = default; 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; }Copy the code

Calendar and Timezone functions

Standard header chrono document

Calendar

Simple date-time conversion

// 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 week days d{w}; // Convert 1 to days STD ::cout << d.c. ount(); hours h{ d }; // Convert 1 to hour STD ::cout << h.mount (); minutes m{ w }; STD ::cout << m.ount (); // Convert 1 to minute STD ::cout << m.ount ();Copy the code

Date and time calculation

struct DaysAttr { sys_days sd; sys_days firstDayOfYear; sys_days lastDayOfYear; year y; month m; day d; weekday wd; }; DaysAttr GetCurrentDaysAttr() {DaysAttr GetCurrentDaysAttr() {DaysAttr; 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; Void OverDaysOfYear() {// This prints out the number of days in the year, with January 1st being the first day, and then prints out the number of days (excluding sd) for the rest of the year. The amount of computation required to perform this operation is small. // Divide each result by days{1} a method can extract the days DN from the entire type and divide it into dl integers for formatting purposes. 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; } / / the number of working days, and the total number of working days of the year void WorkDaysOfYear () {/ / wd is | attr. Wd = attr. Sd | calculation of the week (Monday to Sunday). // To perform this calculation, we first need the first and last dates wd of the year y. | arrt. Y / 1 / arrt. Wd [1] | is wd January first, | arrt. Y / 12 / arrt wd [last] | is wd last December. // wd the total in a year is just the number of weeks between these two dates (plus 1). The subexpression [lastwd-firstwd] is the number of days between two dates. Dividing this result by 1 week yields an integer type that holds the number of weeks between the two dates. / / the number of weeks calculation method and calculation method of the total number of weeks, the same number of day is different from that day start instead of wd on the last day of a year start | sd - firstWd |. 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;; } / / the number of working days and working days in a month total void WorkDaysAndMonthOfYear () {/ / from wd monthly for the first and the last to start | arrt. Y/arrt m |, rather than the entire annual start 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;; } // Days in a year 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;; } / / the number of days in a month void DaysOfMonth () {/ / expression | arrt. Y/arrt. M/last year | is - on the last day of the month, | arrt. Y/arrt m is | | arrt. Y/arrt. M / The first day of the | 1 month. // Both are converted to sys_days, so you can subtract them to get the number of days between them. The count from 1 increases by 1. 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;; }Copy the code

Syntax initialization

For those developers who don’t like “regular syntax”, you can use the full “constructor syntax” instead.

For example, sys_days newYear = y/1/1; sys_days firstWd = y/1/wd[1]; sys_days lastWd = y/12/wd[last]; The value can be replaced with: 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}};Copy the code

Timezone

Time_zone indicates all time zone transitions for a specific geographic area. C++ standard remember to select: / STD: C++ latest

Example:

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'; }}}Copy the code

Consteval and constinit

constexpr

Constexpr can be used for compilation or runtime functions, and its result is a constant. The primary purpose of constEXPr is to declare that the value of a variable or the return value of a function can be used in constant expressions (that is, expressions that can be evaluated at compile time).

int Func() { return 1; } //constexpr int Func() {// return 1; //} constexpr const int x = 5; // OK constexpr const int y = Func(); // ErrorCopy the code

consteval

Can only participate in function declarations when a function is declared using consteval, then all of the operations that call that function with an evaluation must be constant expressions. A function that is actually run at compile time, that is, its arguments are “deterministic” (constant) at compile time, and its results are constant.

Example:

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;
}
Copy the code

As shown in the previous example: ret is a variable returned by a function, and a function defined by Consteval must be able to run constant results at compile time, so it conflicts. Int val = Test1(ret) cannot be called. Constexpr can be used both at compile time and at run time, and therefore can accept variable arguments.

constinit

Constinit can only be used with static or thread_local variables. It cannot be used with constexpr or CONSTeval. A constinit is used to explicitly specify that a variable should be initialized statically. Its life cycle must be static or thread-local (that is, not local), and its initialization expression must be a constant expression.

Constexpr variables are const, read-only, and cannot be changed twice. Constinit means that a variable is initialized at the beginning of the program. It’s static, it can’t be created at run time. It shouldn’t be const, it can be changed twice.

Example:

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, 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; }Copy the code

Use the using reference enum type

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";
}
Copy the code

Implement the mapping of enumeration quantifiers to enumeration quantifiers. A library dedicated to enum conversions is recommended —-Better Enums