C++17

C++17 是C++標準委員會於2017年制定的標準（ISO/IEC 14882），接替C++14標準，後被C++20標準取代。

**歷史**
在C++標準委員會制定此次的三年期標準之前，C++17標準的發佈日期是不確定的。在這段日期中，C++17草案（draft）也被稱為C++1z，正如C++11草案名C++0x或C++1x，C++14草案名C++1y。C++17標準於2017年3月完成國際標準草案（Draft International Standard DIS）階段，並於同年12月完成最終標準發佈。17標準對C++語法進行了加強，並對標準模板庫進行了些許修改，如給庫中算法增加了執行策略execution以支持並行計算。

**新功能**
 靜態斷言static_assert無需提供出錯信息- 移除 trigraphs- 具有模板形式的模板（template template）參數允許使用 typename（之前僅允許使用 class）- std::uncaught_exceptions 取代 std::uncaught_exception- 變長參數模板的Folding運算- 容器存取操作表示方法的統一化（Uniform container access）- 連續型迭代器（Contiguous Iterators）- 新增特殊數學函數- 引進Library Fundamentals TS I中的內容

**新的語言特性**

**類模板的模板參數推導**
模板類構造函數能自動推導模板參數的類型(Class template argument deduction, CTAD)。例如允許以pair(5.0, false) 取代pair(5.0, false)
template 
struct MyContainer {
T val;
MyContainer() : val{} {}
MyContainer(T val) : val{val} {}
// ...
};
MyContainer c1 {1}; // OK MyContainer
MyContainer c2; // OK MyContainer
任何指定變量或變量模板的初始化的聲明，其聲明的類型是類模板（可能是 cv 限定的）。如std::pair p(2, 4.5);     // deduces to std::pair p(2, 4.5);
new表達式，如templatestruct A{ A(T, T);}; auto y = new A{1, 2}; // allocated type is A

**用auto聲明非類型模板參數**
依據auto的推到規則，在可允許類型的非類型模板參數情況下，可從實參類型推導出模板參數：
template 
struct my_integer_sequence {
// Implementation here ...
};
// Explicitly pass type `int` as template argument.
auto seq = std::integer_sequence();
// Type is deduced to be `int`.
auto seq2 = my_integer_sequence();

**摺疊表達式**
摺疊表達式在運算符上執行模板參數包的展開計算。
*單元摺疊：形如 (... op e) or (e op ...)表達式，其中 op 是摺疊運算符，e 是未展開的模板參數包。
*二元摺疊：形如(e1 op ... op e2)表達式， 其中 op 是摺疊運算符，e1或e2 是未展開的模板參數包。
template 
bool logicalAnd(Args... args) {
// Binary folding.
return (true && ... && args);
}
bool b = true;
bool& b2 = b;
logicalAnd(b, b2, true); // == true
template 
auto sum(Args... args) {
// Unary folding.
return (... + args);
}
sum(1.0, 2.0f, 3); // == 6.0

**大括號初始化列表的新的自動推導規則**
以前auto x {3}; 將被推導為std::initializer_list, 現在推導為int。
auto x1 {1, 2, 3}; // error: not a single element
auto x2 = {1, 2, 3}; // x2 is std::initializer_list
auto x3 {3}; // x3 is int
auto x4 {3.0}; // x4 is double

**constexpr lambda**
auto identity = (int n) constexpr { return n; };
static_assert(identity(123) == 123);
constexpr auto add = (int x, int y) {
auto L = = { return x; };
auto R = = { return y; };
return = { return L() + R(); };
};
static_assert(add(1, 2)() == 3);
constexpr int addOne(int n) {
return n { return n + 1; }();
}
static_assert(addOne(1) == 2);

**Lambda函數中按值捕獲this指針**
以前this指針在lambda函數只能按引用捕獲。現在 *this 捕獲對象的副本，而 this (C++11)繼續捕獲引用。
struct MyObj {
int value {123};
auto getValueCopy() {
return *this { return value; };
}
auto getValueRef() {
return this { return value; };
}
};
MyObj mo;
auto valueCopy = mo.getValueCopy();
auto valueRef = mo.getValueRef();
mo.value = 321;
valueCopy(); // 123
valueRef(); // 321

**內聯變量**
過去關鍵字inline可用於函數聲明，現在也可以用於變量聲明，表示函數或定義可定義（但內容必須完全相同）多次。這允許在頭文件中定義一個內聯變量。- 內聯的函數或變量必須在編譯單元內可達（但不一定在訪問點之前）- 在每個編譯單元中都是內聯聲明的- 內聯變量必須是靜態存儲器（類靜態成員或命名空間作用域變量），不能用於塊作用域變量或函數作用域變量。
struct S {
S() : id{count++} {}
~S() { count--; }
int id;
static inline int count{0}; // declare and initialize count to 0 within the class
};

**嵌套的命名空間**
如下：
namespace A {
namespace B {
namespace C {
int i;
}
}
}
//The code above can be written like this:
namespace A::B::C {
int i;
}

**結構化綁定**
變量定義初始化時，允許形如 auto  x, y, z  = expr;，其中expr的“元組類似的對象”包括std::tuple, std::pair, std::array等聚合結構。
using Coordinate = std::pair;
Coordinate origin() {
return Coordinate{0, 0};
}
const auto  x, y  = origin();
x; // == 0
y; // == 0
std::unordered_map mapping {
{"a", 1},
{"b", 2},
{"c", 3}
};
// Destructure by reference.
for (const auto& key, value : mapping) {
// Do something with key and value
}

**帶初始化的選擇語句**
if和switch語句允許如下的變量聲明和初始化：
{
std::lock_guard lk(mx);
if (v.empty()) v.push_back(val);
}
// vs.
if (std::lock_guard lk(mx); v.empty()) {
v.push_back(val);
}
Foo gadget(args);
switch (auto s = gadget.status()) {
case OK: gadget.zip(); break;
case Bad: throw BadFoo(s.message());
}
// vs.
switch (Foo gadget(args); auto s = gadget.status()) {
case OK: gadget.zip(); break;
case Bad: throw BadFoo(s.message());
}

**constexpr if**
//適用場景1：簡化模版偏特化的寫法
template 
constexpr bool isIntegral() {
if constexpr (std::is_integral::value) {
return true;
} else {
return false;
}
}
static_assert(isIntegral() == true);
static_assert(isIntegral() == true);
static_assert(isIntegral() == false);
struct S {};
static_assert(isIntegral() == false);
//適用場景2：編寫變參模版函數
template
int Sum()
{
if constexpr(0 == sizeof...(Ns))
return N;
else
return N+Sum ();
}
//使用場景3：替代enable_if。編寫模板函數時，經常要使用enable_if語句來進行靜態類型檢查，保證模板輸入的類型滿足某種要求
template 
bool IsOdd(T input){
if constexpr (std::is_integral::value) //
return static_cast(input % 2);
}

**utf8字面量**
如下：
char x = u8'x';

**枚舉類型變量直接用大括號初始化**
enum byte : unsigned char {};
byte b {0}; // OK
byte c {-1}; // ERROR
byte d = byte{1}; // OK
byte e = byte{256}; // ERROR

**fallthrough, nodiscard, maybe_unused特性**
C++17 引入了3個特性（attribute）: fallthrough, nodiscard, maybe_unused.
//fallthrough指示編譯器直通一個switch語句：
switch (n) {
case 1: 
// ...
fallthrough
case 2:
// ...
break;
}
//nodiscard引發一個警告，當具有該特性的函數或類的返回值被忽略：
nodiscard bool do_something() {
return is_success; // true for success, false for failure
}
do_something(); // warning: ignoring return value of 'bool do_something()',
// declared with attribute 'nodiscard'
// Only issues a warning when `error_info` is returned by value.
struct nodiscard error_info {
// ...
};
error_info do_something() {
error_info ei;
// ...
return ei;
}
do_something(); // warning: ignoring returned value of type 'error_info',
// declared with attribute 'nodiscard'
//maybe_unused告訴編譯器變量或參數可能未被使用：
void my_callback(std::string msg, maybe_unused bool error) {
// Don't care if `msg` is an error message, just log it.
log(msg);
}

**__has_include運算符**
__has_include運算符用在#if 和 #elif表達式，以檢查是否一個頭文件或源文件可否包含。
//下例有兩個相同作用的頭文件，如果主文件不能包含， 就使用backup/experimental頭文件：
#ifdef __has_include
#  if __has_include()
#    include 
#    define have_optional 1
#  elif __has_include()
#    include 
#    define have_optional 1
#    define experimental_optional
#  else
#    define have_optional 0
#  endif
#endif
//也可以用在不同位置、不同名字但具有相同地位的頭文件的包含：
#ifdef __has_include
#  if __has_include()
#    include 
#    include 
#  elif __has_include()
#    include 
#    include 
#  else
#    error No suitable OpenGL headers found.
# endif
#endif

**新的庫特性**

**std::variant**
std::variant模板類實現了類型安全的union
std::variant v{ 12 };
std::get(v); // == 12
std::get(v); // == 12
v = 12.0;
std::get(v); // == 12.0
std::get(v); // == 12.0

**std::optional**
std::optional類模板可能包含一個值或者可能不包含值：
std::optional create(bool b) {
if (b) {
return "Godzilla";
} else {
return {};//或者 return std::nullopt;
}
}
create(false).has_value();//返回true或false
*create(false);//返回值；如果無值，則返回T()
create(false).value_or("empty"); // == "empty"
create(true).value(); // == "Godzilla"
// optional-returning factory functions are usable as conditions of while and if
if (auto str = create(true)) {
// ...
}

**std::any**
std::any模板類是類型安全的包含任何類型的值：
std::any x {5};
x.has_value() // == true
std::any_cast(x) // == 5
std::any_cast(x) = 10;
std::any_cast(x) // == 10

**std::string_view**
std::string_view類是不擁有字符串的情況可以讀取其值：
// Regular strings.
std::string_view cppstr {"foo"};
// Wide strings.
std::wstring_view wcstr_v {L"baz"};
// Character arrays.
char array3 = {'b', 'a', 'r'};
std::string_view array_v(array, std::size(array));
std::string str {"   trim me"};
std::string_view v {str};
v.remove_prefix(std::min(v.find_first_not_of(" "), v.size()));
str; //  == "   trim me"
v; // == "trim me"
std::string_view不提供c_str()成員函數。

**std::invoke**
std::invoke用於調用“可調用對象”。
template 
class Proxy {
Callable c;
public:
Proxy(Callable c): c(c) {}
template 
decltype(auto) operator()(Args&&... args) {
// ...
return std::invoke(c, std::forward(args)...);
}
};
auto add = (int x, int y) {
return x + y;
};
Proxy p {add};
p(1, 2); // == 3

**std::apply**
std::apply把std::tuple參數應用於可調用對象：
auto add = (int x, int y) {
return x + y;
};
std::apply(add, std::make_tuple(1, 2)); // == 3

**std::filesystem**
std::filesystem可操作文件、目錄、路徑：
const auto bigFilePath {"bigFileToCopy"};
if (std::filesystem::exists(bigFilePath)) {
const auto bigFileSize {std::filesystem::file_size(bigFilePath)};
std::filesystem::path tmpPath {"/tmp"};
if (std::filesystem::space(tmpPath).available > bigFileSize) {
std::filesystem::create_directory(tmpPath.append("example"));
std::filesystem::copy_file(bigFilePath, tmpPath.append("newFile"));
}
}

**std::byte**
std::byte類型既不是字符類型，也不是算術類型，實際上是枚舉類型：
std::byte a {0};
std::byte b {0xFF};
int i = std::to_integer(b); // 0xFF
std::byte c = a & b;
int j = std::to_integer(c); // 0

**maps 和 sets 上更有效率地移動節點**
零開銷的節點操作：
//Moving elements from one map to another:
std::map src ;
std::map dst ;
dst.insert(src.extract(src.find(1))); // Cheap remove and insert of { 1, "one" } from `src` to `dst`.
dst.insert(src.extract(2)); // Cheap remove and insert of { 2, "two" } from `src` to `dst`.
// dst == { { 1, "one" }, { 2, "two" }, { 3, "three" } };
//Inserting an entire set:
std::set src {1, 3, 5};
std::set dst {2, 4, 5};
dst.merge(src);
// src == { 5 }
// dst == { 1, 2, 3, 4, 5 }
//Inserting elements which outlive the container:
auto elementFactory() {
std::set s;
s.emplace(...);
return s.extract(s.begin());
}
s2.insert(elementFactory());
//Changing the key of a map element:
std::map m ;
auto e = m.extract(2);
e.key() = 4;
m.insert(std::move(e));
// m == { { 1, "one" }, { 3, "three" }, { 4, "two" } }

**並行算法**
許多STL算法，如copy, find 和 sort，支持並行執行政策：seq, par, par_unseq, unseq分別表示"sequentially", "parallel", "parallel unsequenced", "unsequenced".
std::vector longVector;
// Find element using parallel execution policy
auto result1 = std::find(std::execution::par, std::begin(longVector), std::end(longVector), 2);
// Sort elements using sequential execution policy
auto result2 = std::sort(std::execution::seq, std::begin(longVector), std::end(longVector));

**注釋**

**另見**
C++
*C++14
*C++11
category:C++