摘要訊息 : 一些 C++ 17 引入的小特性.
0. 前言
C++ 17 引入的絕大多數重要特性我們基本都介紹完了, 可以在 Jonny'Blog 中搜尋 C++ 17 閱覽這些文章. 還有一些小特性也是 C++ 17 引入的, 本文文章將通過羅列的方式介紹一部分特性.
更新紀錄 :
- 2022 年 6 月 5 日進行第一次更新和修正.
1. 擴展的 static_assert
C++ 11 引入 static_assert
關鍵字之後, 有不少討論都是為什麼 static_assert
必須要給出錯誤指引字串. 有時候為了方便和對付編碼錯誤, 我們可能會這樣去寫 static_assert(constant_boolean_expression, "");
. 有不少人都建議, 後面的字串應該是可選的. C++ 17 提案 N3928《Extending static_assert, v2》提出了不帶有信息的 static_assert
, 也就是說, 現在可以這樣寫 : static_assert(constant_boolean_expression);
.
2. 移除三字符組
在很早之前, 電腦的鍵盤並不能輸入所有程式設計所必須的字元, 因此 C 引入了三字符組 :
三字符組 | 替換為 |
---|---|
??= | # |
??/ | \ |
??' | ^ |
??( | [ |
??) | ] |
??! | | |
??< | { |
??> | } |
??- | ~ |
如今, 這些三字符組已經沒有存在的必要了. 所以, C++ 17 提案 N4086《Removing trigraphs??!》提出了移除這些特性. 雖然這篇標題上寫的是三字符組 ??!
, 但是實際上所有的三字符組都被移除了.
3. 樣板參數中的樣板
在 C++ 17 之前, 樣板參數中的樣板只能寫成 template <template <typename> class T>
, 因為T
必定是一個樣板類別, 所以不能把 class
改為 typename
. C++ 17 提案 N4051《Allow typename
in a template template parameter》提出解除這個限制. 因此現在樣板參數中的樣板可以寫成 template <template <typename> typename T>
.
4. UTF-8 前綴
C++ 20 引入了 char8_t
, 但是在此之前的 C++ 17 提案 N4267《Adding u8 character literals》已經提出了引入了 UTF-8 字元或者字串前綴. 為了表示一個 UTF-8 字元, 我們可以在字元前面增加 u8
: char c {u8'\\'};
. 為了表示一個 UTF-8 字串, 我們可以在字串前面增加 u8
:
. 在 C++ 20 之後, 應該把 const char str[] {u8"abc"};
char
型別更正為 char8_t
.
5. 多重巢狀名稱空間
對於多重的巢狀名稱空間, 在 C++ 17 之前必須明確一層一層地寫出 :
namespace A {
namespace B {
namespace C {
namespace D {
//...
}
}
}
}
但是, 如果除了最後一個名稱空間中有內容, 剩餘的名稱空間中都不存在任何內容, 那麼這樣寫顯得十分繁瑣. C++ 17 提案 N4230《Nested namespace definition (revision 2)》提出了簡化版本的寫法 : namespace A::B::C::D { /* ... */ }
. 如果名稱空間 A
, B
和 C
之前沒有被宣告, 那麼這樣的寫法相當於宣告了這些名稱空間.
6. 名稱空間和列舉的屬性
屬性這個特性在 C++ 11 被正式引入, 但是它們在 C++ 17 之前暫時無法用於列舉和名稱空間. C++ 17 提案 N4266《Attributes for namespaces and enumerators》提出為名稱空間和列舉增加屬性支援. 對於名稱空間的屬性可以寫在 namespace
和名稱空間的名字之間 : namespace [[attribution]] A { /* ... */ }
. 對於列舉也是類似, 當然列舉還還支援屬性寫在型別之後 : enum [[attribution_1]] A : int [[attribution_2]] { /* ... */ }
.
7. 非型別樣板引數的轉型
對於非型別樣板引數, 其轉型限制是比較多的. C++ 17 提案 N4268《Allow constant evaluation for all non-type template arguments》提出了解除一些限制, 允許 :
- 陣列至陣列指標的轉型;
- 函式至函式指標的轉型;
- 限定符的轉型;
nullptr
向任意指標型別和成員指標型別的轉型.
另外, 針對非型別樣板引數, 在 C++ 17 中只要不是
- 子物件 (陣列中的元素, 成員變數),
- 臨時物件,
- 字面值字串,
typeid
表達式的結果,- 預先定義的
__func__
變數,
幾乎都可以支援.
int arr[] {1, 2, 3};
struct S {
int a;
static int b;
} s;
template <int *>
class Foo {};
int main(int argc, char *argv[]) {
Foo<&arr[1]> f1; // Error
Foo<arr> f2; // OK
Foo<&s.a> f3; // Error
Foo<&s.b> f4; // OK
}
8. 移除 register
關鍵字
早在 C++ 11 時, register
關鍵字已經被遺棄, C++ 17 提案 P0001R1《Remove Deprecated Use of the register
Keyword》提出從 C++ 中移除這個關鍵字, 作為保留以供未來使用. register
關鍵字是用於標識一個變數 register int a = 0;
來告訴編碼器這個變數 a
應該放置在暫存器中. 但是現代編碼器幾乎不需要用戶明確標識, 編碼器自身就可以判斷某個變數是否適合存放於某個存儲中. 因此, 這個關鍵字對於 C++ 的影響已經近乎為零.
9. 移除 operator++(bool)
C++ 17 提案 P0002R1《Remove Deprecated operator++(bool)
》提出, 移除早在 C++ 98 中已經被遺棄的特性 : 針對 bool
型別變數的 ++
運算 :
auto b {false};
b++;
++b;
上述寫法在 C++ 17 中已經不會被編碼器編碼通過.
10. __has_include
C++ 17 提案 P0061R1《__has_include
for C++17》為 C++ 帶來了一個巨集 __has_include
. 它用於判定某個標頭檔是否已經被包含 :
#if __has_include(<iostream>)
using std::endl;
#else
#include <iostream>
using std::endl;
#endif
11. 改進 Range-For
目前的 Range-For 限制過多, 例如尾後疊代器無法在 Range-For 中進行前進或者後退等的操作以及兩個疊代器的型別必須相同等等. C++ 17 提案 P0061R1 《Generalizing the Range-Based For Loop》提出解除這些限制, 實際上是為了 C++ 20 中的 Ranges 準備的 (在當時還是 Ranges TS). 一個 Range-For 原來會被這樣解釋 :
{
auto &&range {for-range-init};
for(auto begin {begin-init}, end {end-init}; begin not_eq end; ++begin) {
for-range-decl = *begin;
// ...
}
}
使用 auto
進行宣告就必定代表著 begin
和 end
的型別相同. 而 P0061R1 提出解除限制後, 一個 Range-For 就可能會被這樣解釋 :
{
auto &&range {for-range-init};
auto begin {begin-init};
auto end {end-init};
for(; begin not_eq end; ++begin) {
for-range-decl = *begin;
// ...
}
}
12. 為 enum class
增加建構規則
在 C++ 11 引入 enum class
之後, C++ 中就存在一種宣告獨一無二的整型型別的方式 : enum class E : unsigned long {};
. 所有型別為 E
的變數都不會向其它整型型別發生轉型. 如果某個時刻, 我們恰好需要這樣的型別以避免程式碼交互過程中的轉型, 那麼使用 enum class
來宣告就非常合適. 但是, 如果需要通過整型型別來宣告 E
的變數, 是需要進行轉型的 : E a1 {static_cast<E>(1)};
. 但是, 如果這樣去寫, 就不需要轉型 :
struct Foo {
unsigned long a;
};
Foo a0 {1}; // OK
Foo a1 = {2}; // OK
借鑑類似的程式碼, C++ 17 提案 P0138R2《Construction Rules for enum class
Values》除了提出為 enum class
增加建構規則之後, 還避免了上述兩個情況發生衝突. 在原類別建構規則不變的情況下, 允許非縮小轉換下, 利用其它型別來初始化一個限定作用範圍的列舉型別 :
enum class E {};
E func(E) {
return {1}; // Error
}
int main(int argc, char *argv[]) {
E e1 {1}; // OK
E e2 = 1; // Error
E e3 = {1}; // Error
E e4(1); // Error
E e5(E {1}); // OK
E e6 {E {1}}; // OK
func({1}); // Error
func(E {1}); // OK
E *p {new E {1}}; // OK
}
13. 十六進制浮點數字面值
從 C++ 11 起, 標準樣板程式庫已經支援十六進制的浮點數常數的輸入與輸出, 但是這並不是作為核心語言的部分. C++ 17 提案 P0245R1《Hexadecimal floating literals for C++》提出, 讓 C++ 支援十六進制的浮點數字面值常數 :
#include <iostream>
int main(int argc, char *argv[]) {
std::cout << 0x10.1p0 << std::endl; // 輸出結果 : 16.0625
}
其中, p
是一種記數法則, 類似於 e
. 上述程式碼中的 0x10.1p0 = 10.1_{\mathrm {hex}} \times 2^{0}
.
14. 屬性名稱空間
C++ 17 為名稱空間引入了屬性, C++ 17 提案 P0028R4《Using attribute namespaces without repetition》增加了屬性名稱空間的支援 : namespace [[attribute namespace::attr]] A { /* ... */ }
.
15. 非標準屬性支援
編碼器通常會支援比 C++ 標準更多的屬性, 因此 C++ 17 提案 P0283R2《Standard and non-standard attributes》提出, 對於未知的屬性應該作忽略處理 (一般來說, 編碼器可能會擲出一個警告). 這大大提高了 C++ 程式碼在不同編碼器之間的可攜性. 例如,
int [[unknown_attribute]] a;
[[abc]] int [[ccc]] b [[lll]];
在 Clang 下會產生如下的警告 :
main.cpp:5:11: warning: unknown attribute 'unknown_attribution' ignored [-Wunknown-attributes]
int [[unknown_attribution]] a;
^
main.cpp:5:11: warning: unknown attribute 'unknown_attribution' ignored [-Wunknown-attributes]
main.cpp:6:19: warning: unknown attribute 'ccc' ignored [-Wunknown-attributes]
[[abc]] int [[ccc]] b [[lll]];
^
main.cpp:6:7: warning: unknown attribute 'abc' ignored [-Wunknown-attributes]
[[abc]] int [[ccc]] b [[lll]];
^
main.cpp:6:19: warning: unknown attribute 'ccc' ignored [-Wunknown-attributes]
[[abc]] int [[ccc]] b [[lll]];
^
main.cpp:6:29: warning: unknown attribute 'lll' ignored [-Wunknown-attributes]
[[abc]] int [[ccc]] b [[lll]];
^
6 warnings generated.
自創文章, 原著 : Jonny. 如若閣下需要轉發, 在已經授權的情況下請註明本文出處 :