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無需提供出錯信息[1]
  • 移除 trigraphs[2][3]
  • 具有模板形式的模板(template template)参数允许使用 typename(之前仅允许使用 class)[4]
  • std::uncaught_exceptions 取代 std::uncaught_exception[5][6]
  • 变长参数模板的Folding运算[6][7]
  • 容器存取操作表示方法的统一化(Uniform container access)[8][9]
  • 连续型迭代器(Contiguous Iterators)[8][10]
  • 新增特殊數學函數[11]
  • 引进Library Fundamentals TS I中的内容[12]

类模板的模板参数推导编辑

模板类构造函数能自动推导模板参数的类型(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声明非类型模板参数编辑

依据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); // == truetemplate <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[13][14]

auto x1 {1, 2, 3}; // error: not a single elementauto x2 = {1, 2, 3}; // x2 is std::initializer_list<int>auto x3 {3}; // x3 is intauto 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(); // 123valueRef(); // 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};

嵌套的命名空间编辑

如下:[14][15]

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; // == 0y; // == 0std::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());}

constexpr if编辑

//适用场景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);}

utf8字面量编辑

如下:[6][16]

char x = u8'x';

枚举类型变量直接用大括号初始化编辑

enum byte : unsigned char {};byte b {0}; // OKbyte c {-1}; // ERRORbyte d = byte{1}; // OKbyte 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(<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编辑

std::variant模板类实现了类型安全的union

std::variant<int, double> v{ 12 };std::get<int>(v); // == 12std::get<0>(v); // == 12v = 12.0;std::get<double>(v); // == 12.0std::get<1>(v); // == 12.0

std::optional编辑

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 ifif (auto str = create(true)) {  // ...}

std::any编辑

std::any模板类是类型安全的包含任何类型的值:

std::any x {5};x.has_value() // == truestd::any_cast<int>(x) // == 5std::any_cast<int&>(x) = 10;std::any_cast<int>(x) // == 10

std::string_view编辑

std::string_view类是不拥有字符串的情况可以读取其值:[17]

// 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编辑

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::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可操作文件、目录、路径:[18]

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<int>(b); // 0xFFstd::byte c = a & b;int j = std::to_integer<int>(c); // 0

maps 和 sets 上更有效率地移动节点编辑

零开销的节点操作:[19][8]

//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".[20]

std::vector<int> longVector;// Find element using parallel execution policyauto result1 = std::find(std::execution::par, std::begin(longVector), std::end(longVector), 2);// Sort elements using sequential execution policyauto result2 = std::sort(std::execution::seq, std::begin(longVector), std::end(longVector));

注釋编辑

  1. ^ N3928: Extending static_assert, v2 (Walter E. Brown) (PDF). [2015-07-16]. (原始内容存档 (PDF)于2015-08-11). 
  2. ^ N3981: Removing trigraphs??! (Richard Smith). 2014-05-06 [2015-07-16]. (原始内容存档于2018-07-09). 
  3. ^ 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
  4. ^ N4051: Allow typename in a template template parameter (Richard Smith). [2015-07-16]. (原始内容存档于2015-08-11). 
  5. ^ N4259: Wording for std::uncaught_exceptions (Herb Sutter) (PDF). [2015-07-16]. (原始内容 (PDF)存档于2014-11-29). 
  6. ^ 6.0 6.1 6.2 New core language papers adopted for C++17. [2015-07-16]. (原始内容存档于2015-04-27). 
  7. ^ N4295: Folding expressions (Andrew Sutton, Richard Smith). [2015-07-16]. (原始内容存档于2015-04-04). 
  8. ^ 8.0 8.1 8.2 New standard library papers adopted for C++17. [2015-07-16]. (原始内容存档于2014-11-29). 
  9. ^ N4280: Non-member size() and more (Riccardo Marcangelo) (PDF). [2015-07-16]. (原始内容存档 (PDF)于2015-03-09). 
  10. ^ N4284: Contiguous Iterators (Jens Maurer). [2015-07-16]. (原始内容存档于2014-11-29). 
  11. ^ Mathematical Special Functions for C++17, v5 (PDF). [2016-08-02]. (原始内容存档 (PDF)于2016-04-05). 
  12. ^ Adopt Library Fundamentals V1 TS Components for C++17 (R1). [2016-08-02]. (原始内容存档于2016-04-05). 
  13. ^ N3922: New Rules for auto deduction from braced-init-list (James Dennett). [2015-07-16]. (原始内容存档于2015-08-10). 
  14. ^ 14.0 14.1 Updates to my trip report. [2015-07-16]. (原始内容存档于2015-03-19). 
  15. ^ N4230: Nested namespace definition (Robert Kawulak, Andrew Tomazos). [2015-07-16]. (原始内容存档于2015-08-03). 
  16. ^ N4267: Adding u8 character literals (Richard Smith). [2015-07-16]. (原始内容存档于2015-10-28). 
  17. ^ std::basic_string_view - cppreference.com. en.cppreference.com. [2016-06-23]. (原始内容存档于2016-06-17). 
  18. ^ Filesystem Library Proposal (Beman Dawes). [2016-08-02]. (原始内容存档于2016-07-20). 
  19. ^ N4279: Improved insertion interface for unique-key maps (Thomas Köppe). [2015-07-16]. (原始内容存档于2015-04-27). 
  20. ^ The Parallelism TS Should be Standardized. [2016-08-02]. (原始内容存档于2016-04-05). 

另見编辑