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++语法进行了加强，并对标准模板库进行了些许修改，如给<algorithm>库中算法增加了执行策略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<double,bool>(5.0, false) template <typename T = float> struct MyContainer { T val; MyContainer() : val{} {} MyContainer(T val) : val{val} {} // ... }; MyContainer c1 {1}; // OK MyContainer<int> MyContainer c2; // OK MyContainer<float> 任何指定变量或变量模板的初始化的声明，其声明的类型是类模板（可能是 cv 限定的）。如std::pair p(2, 4.5); // deduces to std::pair<int, double> p(2, 4.5); new表达式，如template<class T>struct A{ A(T, T);}; auto y = new A{1, 2}; // allocated type is A<int> 依据auto的推到规则，在可允许类型的非类型模板参数情况下，可从实参类型推导出模板参数： template <auto... seq> struct my_integer_sequence { // Implementation here ... }; // Explicitly pass type `int` as template argument. auto seq = std::integer_sequence<int, 0, 1, 2>(); // Type is deduced to be `int`. auto seq2 = my_integer_sequence<0, 1, 2>(); 折叠表达式在运算符上执行模板参数包的展开计算。 单元折叠：形如 (... op e) or (e op ...)表达式，其中 op 是折叠运算符，e 是未展开的模板参数包。 二元折叠：形如(e1 op ... op e2)表达式， 其中 op 是折叠运算符，e1或e2 是未展开的模板参数包。 template <typename... Args> bool logicalAnd(Args... args) { // Binary folding. return (true && ... && args); } bool b = true; bool& b2 = b; logicalAnd(b, b2, true); // == true template <typename... Args> auto sum(Args... args) { // Unary folding. return (... + args); } sum(1.0, 2.0f, 3); // == 6.0 以前auto x {3}; 将被推导为std::initializer_list<int>, 现在推导为int。 auto x1 {1, 2, 3}; // error: not a single element auto x2 = {1, 2, 3}; // x2 is std::initializer_list<int> auto x3 {3}; // x3 is int auto x4 {3.0}; // x4 is double 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); 以前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<int, int>; Coordinate origin() { return Coordinate{0, 0}; } const auto [ x, y ] = origin(); x; // == 0 y; // == 0 std::unordered_map<std::string, int> 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<std::mutex> lk(mx); if (v.empty()) v.push_back(val); } // vs. if (std::lock_guard<std::mutex> 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()); } //适用场景1：简化模版偏特化的写法 template <typename T> constexpr bool isIntegral() { if constexpr (std::is_integral<T>::value) { return true; } else { return false; } } static_assert(isIntegral<int>() == true); static_assert(isIntegral<char>() == true); static_assert(isIntegral<double>() == false); struct S {}; static_assert(isIntegral<S>() == false); //适用场景2：编写变参模版函数 template<int N, int...Ns> int Sum() { if constexpr(0 == sizeof...(Ns)) return N; else return N+Sum<Ns...> (); } //使用场景3：替代enable_if。编写模板函数时，经常要使用enable_if语句来进行静态类型检查，保证模板输入的类型满足某种要求 template <typename T> bool IsOdd(T input){ if constexpr (std::is_integral<T>::value) // return static_cast<bool>(input % 2); } 如下： 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 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运算符用在#if 和 #elif表达式，以检查是否一个头文件或源文件可否包含。 //下例有两个相同作用的头文件，如果主文件不能包含， 就使用backup/experimental头文件： #ifdef __has_include # if __has_include(<optional>) # include <optional> # define have_optional 1 # elif __has_include(<experimental/optional>) # include <experimental/optional> # define have_optional 1 # define experimental_optional # else # define have_optional 0 # endif #endif //也可以用在不同位置、不同名字但具有相同地位的头文件的包含： #ifdef __has_include # if __has_include(<OpenGL/gl.h>) # include <OpenGL/gl.h> # include <OpenGL/glu.h> # elif __has_include(<GL/gl.h>) # include <GL/gl.h> # include <GL/glu.h> # else # error No suitable OpenGL headers found. # endif #endif std::variant模板类实现了类型安全的union std::variant<int, double> v{ 12 }; std::get<int>(v); // == 12 std::get<0>(v); // == 12 v = 12.0; std::get<double>(v); // == 12.0 std::get<1>(v); // == 12.0 std::optional类模板可能包含一个值或者可能不包含值： std::optional<std::string> 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 x {5}; x.has_value() // == true std::any_cast<int>(x) // == 5 std::any_cast<int&>(x) = 10; std::any_cast<int>(x) // == 10 std::string_view类是不拥有字符串的情况可以读取其值： // Regular strings. std::string_view cppstr {"foo"}; // Wide strings. std::wstring_view wcstr_v {L"baz"}; // Character arrays. char array[3] = {'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用于调用“可调用对象”。 template <typename Callable> class Proxy { Callable c; public: Proxy(Callable c): c(c) {} template <class... Args> decltype(auto) operator()(Args&&... args) { // ... return std::invoke(c, std::forward<Args>(args)...); } }; auto add = [](int x, int y) { return x + y; }; Proxy<decltype(add)> p {add}; p(1, 2); // == 3 std::apply把std::tuple参数应用于可调用对象： auto add = [](int x, int y) { return x + y; }; std::apply(add, std::make_tuple(1, 2)); // == 3 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 a {0}; std::byte b {0xFF}; int i = std::to_integer<int>(b); // 0xFF std::byte c = a & b; int j = std::to_integer<int>(c); // 0 零开销的节点操作： //Moving elements from one map to another: std::map<int, string> src {{1, "one"}, {2, "two"}, {3, "buckle my shoe"}}; std::map<int, string> dst {{3, "three"}}; 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<int> src {1, 3, 5}; std::set<int> 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<int, string> m {{1, "one"}, {2, "two"}, {3, "three"}}; 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<int> 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)); N3928: Extending static_assert, v2 (Walter E. Brown) (PDF). [2015-07-16]. （原始内容存档 (PDF)于2015-08-11）.  N3981: Removing trigraphs??! (Richard Smith). 2014-05-06 [2015-07-16]. （原始内容存档于2018-07-09）.  IBM comment on preparing for a Trigraph-adverse future in C++17 （页面存档备份，存于互联网档案馆）, IBM paper N4210, 2014-10-10. Authors: Michael Wong, Hubert Tong, Rajan Bhakta, Derek Inglis N4051: Allow typename in a template template parameter (Richard Smith). [2015-07-16]. （原始内容存档于2015-08-11）.  N4259: Wording for std::uncaught_exceptions (Herb Sutter) (PDF). [2015-07-16]. （原始内容 (PDF)存档于2014-11-29）.  New core language papers adopted for C++17. [2015-07-16]. （原始内容存档于2015-04-27）.  N4295: Folding expressions (Andrew Sutton, Richard Smith). [2015-07-16]. （原始内容存档于2015-04-04）.  New standard library papers adopted for C++17. [2015-07-16]. （原始内容存档于2014-11-29）.  N4280: Non-member size() and more (Riccardo Marcangelo) (PDF). [2015-07-16]. （原始内容存档 (PDF)于2015-03-09）.  N4284: Contiguous Iterators (Jens Maurer). [2015-07-16]. （原始内容存档于2014-11-29）.  Mathematical Special Functions for C++17, v5 (PDF). [2016-08-02]. （原始内容存档 (PDF)于2016-04-05）.  Adopt Library Fundamentals V1 TS Components for C++17 (R1). [2016-08-02]. （原始内容存档于2016-04-05）.  N3922: New Rules for auto deduction from braced-init-list (James Dennett). [2015-07-16]. （原始内容存档于2015-08-10）.  Updates to my trip report. [2015-07-16]. （原始内容存档于2015-03-19）.  N4230: Nested namespace definition (Robert Kawulak, Andrew Tomazos). [2015-07-16]. （原始内容存档于2015-08-03）.  N4267: Adding u8 character literals (Richard Smith). [2015-07-16]. （原始内容存档于2015-10-28）.  std::basic_string_view - cppreference.com. en.cppreference.com. [2016-06-23]. （原始内容存档于2016-06-17）.  Filesystem Library Proposal (Beman Dawes). [2016-08-02]. （原始内容存档于2016-07-20）.  N4279: Improved insertion interface for unique-key maps (Thomas Köppe). [2015-07-16]. （原始内容存档于2015-04-27）.  The Parallelism TS Should be Standardized. [2016-08-02]. （原始内容存档于2016-04-05）.  C++ C++14 C++11