随着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;
}
當然協程也有一些限制:
- 不能使用變長實參;
- 不能使用普通的 return 語句,或占位符傳回類型(auto 或 Concept);
- 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(特性)
- [[nodiscard( string-literal )]]:忽略傳回值時警告。
- [[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 正式釋出!》,本文僅供參考與學習。最近遇到一些好的文章莫名被删除,出于學習和共享的目的,希望留一個備份。如原作者版權需要請聯系删除,謝謝!