天天看點

C++20已釋出!operator<=>

随着2020年的結束,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 正式釋出!》,本文僅供參考與學習。最近遇到一些好的文章莫名被删除,出于學習和共享的目的,希望留一個備份。如原作者版權需要請聯系删除,謝謝!

繼續閱讀