C++17相比于C++14的所有重大变化

d41d8cd41d8c

译自P0636R1

2017-10-28:更新至P0636R2

摘要

本文档列举了自C++14发布以来,到C++17发布为止,应用于C++工作草案(working draft)的所有重大变化。重大变化(major changes)是以专门的文件(paper)的形式加入的。不是每个文件都单独在此提及,没有单独提及的文件在下面简要列出。CWG或LWG问题清单(issue list)中的问题解决方案(issue resolution)通常不包括在重大变化之内,但是包含问题解决方案的文件也会简要列出。没有写入已发布的国际标准的缺陷报告(defect report)用“DR”标注。(defect report的含义在下文有解释。)

目录

  1. 删除或弃用的特性
  2. 具有全局影响的新的核心语言特性
  3. 具有局部影响的新的核心语言特性
  4. 新的库特性
  5. 对现有特性的修改
  6. 杂项
  7. 未列入的文件
  8. 缺陷、问题、bug修复
  9. 展示C++17的代码片段合集

删除或弃用的特性

文件:N4086
概要:删除trigraph
注释:字符序列 ??! 不再表示 | 。实现(implementation)可以把类似trigraph的功能作为输入编码的一环。

文件:P0001R1
概要:删除register
注释:register仍然是保留字,但是不再具有任何语义。

文件:P0002R1
概要:删除bool类型的++运算
注释:前自增和后自增运算符不再接受bool类型的操作数。

文件:P0003R5
概要:删除throw(A, B, C)
注释:形如throw(A, B, C)的动态异常规范(dynamic exception specification)不再是合法的。throw()保留,成为noexcept(true)的同义词。注意throw()的语义变化。

文件:P0386R2
概要:弃用static constexpr成员的重复声明
注释: 对于struct X { static constexpr int n = 10; };,int X::n;不再是定义,而是多余的重复声明,这种重复声明已被弃用。成员X::n是隐含的inline变量(见下文)。

----------
文件:N4190
概要:删除auto_ptr、random_shuffle、<functional>中的过时部分
注释:在C++11已经被弃用并被更好的组件取代的功能将不再包含于C++17中。它们的名字仍然是保留的,实现可以选择继续提供。

文件:P0004R1
概要:删除被弃用的iostream成员
注释:同上

文件:P0302R1
概要:删除std::function的内存分配器(allocator)支持
注释:多态函数包装器function不再具有接受内存分配器的构造函数。内存分配器支持对具有类型擦除(type-erase)功能的可复制类型来说很困难,可能无法有效实现。

文件:P0063R3(见下文)
概要:弃用一些C库(C library)头文件(header)
注释:“C库”(C library,这个术语指的是C++标准库的一部分, 而不是C标准库的一部分)中的以下头文件被弃用:<ccomplex>、<cstdalign>、<cstdbool>、<ctgmath>。注意<ciso646>头文件没有被弃用。

文件:P0174R2
概要:弃用标准库中的陈旧部分
注释:以下组件被弃用:allocator<void>、raw_storage_iterator、get_temporary_buffer、is_literal_type、std::iterator。

文件:P0618R0
概要:弃用<codecvt>
注释:整个<codecvt>头文件(注意codecvt类并不在这个头文件中)被弃用。wstring_convert和wbuffer_convert也被弃用。这些功能难以正确使用,甚至有怀疑它们的规范是否正确。用户应该使用专用的文本处理库。

文件:P0371R1
概要:暂时弃用memory_order_consume
注释: 当前的“consume”内存序的语义被发现是不够的,需要重新定义。这项工作希望能在C++的下一版中完成。在进行这项工作的时间里,建议用户不要使用“consume”内存序,而是使用“acquire”内存序,以免将来遇到问题。

文件:P0521R0
概要:弃用shared_ptr::unique
注释:该成员函数暗示了没有实际提供的行为。

文件:P0604R0
概要:弃用result_of
注释:请改用新的trait:invoke_result。

具有全局影响的新的核心语言特性

这些是在不需要你知情或同意的情况下,你可能遇到的特性。

文件:P0012R1
概要:异常规范(exception specification)作为类型系统的一部分
注释:函数的异常规范现在是函数类型的一部分:void f() noexcept(true); 和 void f() noexcept(false); 是具有不同类型的函数。函数指针可以按符合常理的方式转换。(但是这两个函数 f 不能构成重载。)这个变化加强了类型系统。例如,API可以通过类型系统要求回调函数不抛出异常。

文件:P0135R1
概要:保证的复制消除(guaranteed copy elision)
注释:prvalue和glvalue的含义已被修改,prvalue不再代表对象(object),而仅仅代表“初始化”(“initialization”)。返回prvalue的函数不再会复制对象(强制复制消除“mandatory copy elision”),并且有了新的从prvalue到glvalue的转换,叫做temporary materialization conversion。这个变化意味着复制消除是保证了的,而且甚至可以应用于不可移动或复制的类型。这允许你定义返回这种(不可移动或复制的)类型的函数。

文件:P0035R4
概要:过度对齐类型(over-aligned types——即alignment超过std::max_align_t的类型)的动态内存分配
注释:动态内存分配(operator new)现在可以支持过度对齐的类型,这个运算符的一个新的重载接受对齐要求(alignment)参数。仍然是由实现决定支持哪些对齐。

文件:P0145R3
概要:更严格的表达式求值顺序
注释:对某些子表达式的求值顺序的规定更多了。这个变化的一个重要方面是函数的各个实参现在以不确定(indeterminate)的顺序的求值(也就是说没有交错 interleaving),而以前只是未指定的(unspecified)。注意重载运算符的求值顺序和调用方式有关:如果按照运算符的形式调用,就和相应的内置运算符的顺序相同;如果按照函数调用的形式调用,就和一般的函数调用相同(也就是不确定的 indeterminate)。

具有局部影响的新的核心语言特性

这些是你在知道之后才能用到的特性。

文件:N4267
概要:u8字符字面量
注释:具有u8前缀的字符字面量产生一个在UTF-8中占有一个编码单元(code unit)的合法Unicode代码点(code point)对应的字符,换句话说,就是产生一个ASCII值。例如:u8'x'。

文件:P0245R1
概要:十六进制浮点数字面量
注释:具有十六进制底数和十进制指数的浮点数字面量:0xC.68p+2、0x1.P-126。C语言自从C99开始就支持这种语法, printf的%a可以输出这种形式的浮点数。

文件:N4295P0036R0
概要:fold表达式
注释:用于迭代地将二元运算符应用于参数包(parameter pack)的元素的便利语法:template <typename ...Args> auto f(Args ...args) { return (0 + ... + args); }

文件:P0127R2
概要:template <auto>
注释:模板的非类型形参可以用占位符类型auto声明。例如:
• template <auto X> struct constant { static constexpr auto value = X; };
• Delegate<&MyClass::some_function>
(←_←第二个例子的完整版本见下文)

文件:P0091R3P0512R0P0433R2P0620R0P0702R2DRP0739R0DR
概要:类模板参数推导
注释:类模板的模板参数现在可以从构造函数推导。例如 pair p(1, 'x'); 定义 p 为 pair<int, char> 类型的变量(这不是HTML错误,模板实参是故意省略的)。显式的推导指引(deduction guide)是隐式推导的补充,显式的推导指引允许作者定制推导如何发生,或者禁止推导。

文件:P0292R2
概要:Constexpr if
注释:新的 if constexpr (condition) 语句根据常量表达式(constant expression)的值选择执行哪个分支。在模板的实例中,只有在条件(condition)具有合适的值的时候,对应的分支才会实例化。

文件:P0305R1
概要:带初始化的选择语句
注释:选择语句 if 和 switch 有了一个新的、可选的初始化部分:if (auto it = m.find(key); it != m.end()) return it->second;

文件:P0170R1
概要:Constexpr lambda
注释:lambda表达式现在可以是常量表达式了:auto add = [](int a, int b) constexpr { return a + b; }; int arr[add(1, 2)];

文件:P0018R3
概要:lambda捕获*this
注释:以前:[self = *this]{ self.f(); } 现在:[*this]{ f(); }

文件:P0386R2P0607R0
概要:inline变量
注释:在头文件中:inline int n = 10; 所有定义都指代同一个实体。static constexpr 成员变量隐含地成为 inline 变量。(标准库中的常量,不论是已有的还是新增的,都已使用inline。)

文件:P0217R3P0615R0
概要:结构化绑定
注释:auto [it, ins] = m.try_emplace(key, a1, a2, a3);
可以分解数组、所有成员都是public的类、像pair、tuple和array一样遵循get<N>协议的自定义类型。

文件:P0061R1
概要:__has_include
注释:检查能否包含特定头文件的预处理运算符。

文件:P0188R1 P0189R1 P0212R1
概要:属性(attribute)[[fallthrough]]、[[nodiscard]]、[[maybe_unused]](分别对应三个文件)
注释:一套新的标准化属性。属性没有必要的语义,但是鼓励实现发出或抑制适当的诊断(警告)。

文件:P0137R1
概要:launder
注释:语言支持工具(“优化屏障optimisation barrier”),允许库重新使用存储(storage),并通过旧指针访问该存储(以前是不允许的)。(这是实现者的专家工具,不应在“正常”代码中出现)。

文件:P0298R3
概要:字节类型
注释:新的类型byte在<cstddef>中定义(不在<stddef.h>,并且只定义在命名空间std中),它和unsigned char具有相同的布局,和现有的字符类型一样允许别名(aliasing),并且定义了按位操作。

新的库特性

文件:P0226R1
概要:数学特殊函数(mathematical special functions)
注释:前国际标准ISO/IEC 29124:2010(数学特殊函数)的内容现已加入C++。 这些函数只添加到<cmath> ,而没有添加到<math.h> ,并且只定义在命名空间std中。

文件:P0218R0P0219R1P0317R1P0392R0P0430R2P0492R2LWG 2956DR
概要:文件系统
注释:文件系统技术规范(Filesystems Technical Specification)的内容现已加入C++。文件系统库允许以可移植的方式与目录和类似目录的结构进行交互(列出文件目录的内容,移动文件等)。 它主要以POSIX为模型,但它足够灵活,可以在各种系统上实现。

文件:P0024R2P0336R1P0394R4P0452R1P0467R2P0502R0P0518R1P0523R1P0574R1P0623R0
概要:并行
注释:并行技术规范(Parallelism Technical Specification)的内容现已加入C++。它为很多算法(algorithm)增加了重载,这些新的重载额外接受一个执行策略execution policy)参数。它也加入了新的算法(见下文)。 支持三种执行策略,分别提供顺序(sequential),并行(parallel)和向量化(vectorized)的执行。

文件:P0024R2
概要:新算法
注释:并行技术规范为标准库添加了几种新算法。加入它们的动机是它们可以有效地并行执行,但也提供了通常的(不接受execution policy参数的)简单形式:for_each_n、reduce、transform_reduce、exclusive_scan、inclusive_scan、transform_exclusive_scan、transform_inclusive_scan。请注意, reduce看起来与现有的accumulate相似,但reduce不保证任何特定的操作顺序。

文件:P0220R1P0254R2P0403R1
概要:新类型:string_view(以及basic_string_view)
注释:新的string_view类是API接口的推荐类型,如果API需要读取字符串的内容,但是不需要取得字符串的所有权或者修改字符串。它可以从char指针构造,但是其他的字符串类型应该自己提供到string_view的隐式转换。

文件:P0220R1P0032R3P0504R0
概要:新类型:any
注释:类型any对可复制(copyable)的对象进行类型擦除(type-erase)。可以用any做的事基本上有以下三件:1. 把T类型的值放入其中。2. 复制它。 3. 检查它是否包含一个U类型的值,并且取出这个值,取出操作只有在U就是T的时候才会成功。

文件:P0088R3P0393R3P0032R3P0504R0P0510R0LWG 2901DR
概要:新的类模板:variant
注释:variant是一个可辨识联合(disjoint union / discriminated union)。variant<A, B, C> 类型的值在任何时间都包含一个类型为A、B或C之一的值。

文件:P0220R1P0307R2P0032R3P0504R0
概要:新的类模板:optional
注释:一个可选的值。一个optional<T>表示一个T值,或者表示没有值(由类型nullopt_t标记)。在某些方面,可以认为它等同于variant<nullopt_t, T>,但是它具有专门的接口。

文件:P0220R1
概要:新算法:sample
注释:从一个范围(range)里均匀选取最多n个元素作为样本。

文件:N4169
概要:invoke
注释:以统一形式调用Callable实体(包括函数、函数对象和成员指针)的工具。这允许用户编写的库可以使用与标准的魔法(magic)INVOKE规则相同的行为。

文件:P0077R2P0604R0
概要:is_invocable, is_invocable_r, invoke_result
注释:用于推断可调用性和调用结果的trait。(←_←看名字就知道应该和std::invoke配合使用。)

文件:P0067R5P0682R1DR
概要:基本字符串转换
注释:函数to_chars和from_chars分别生成和解析数字的字符串表示。它们旨在形成用于替换printf和iostream格式化操作的高效、低级(low-level)的基础。它们遵循惯用的C++算法风格。

文件:N3911
概要:类型别名模板void_t
注释:template <class...> using void_t = void; 在元编程中惊人地有用,简化了SFINAE的使用。

文件:N4389
概要:类型别名模板bool_constant
注释:template <bool B> using bool_constant = integral_constant<bool, B>

文件:P0013R1
概要:逻辑操作元函数
注释:用于元编程的变长元函数conjunction、disjunction和negation。这些trait在元编程的意义上是短路(short-circuit)的:不影响结果的模板不会被实例化。

文件:P0185R1
概要:用于SFINAE友好(SFINAE-friendly)swap的traits
注释:新的trait is_swappable、is_nothrow_swappable、is_swappable_with、is_nothrow_swappable_with。

文件:LWG 2911
概要:Trait is_aggregate
注释:检查类型是否是聚合类(aggregate)。例如,可以用于判断泛型类型(←_←就是作为模板参数的类型)是应该用列表初始化(list-initialization)还是应该用非列表初始化(non-list-initialization)。

文件:P0258R2
概要:Trait has_unique_object_representations
注释:这个trait可以用来判断特定的基于值的操作(例如比较和求hash值)是否可以用基于对象表示(object representation)的操作(例如memcmp)代替。

文件:P0007R1
概要:as_const
注释:给定lvalue x,std::as_const(x)返回相应的带const限定符的lvalue。std::as_const不能接受rvalue。

文件:N4280
概要:非成员函数size、data、empty
注释:新增的函数补充了现有的非成员函数begin、end等等。通过这些函数,可以用统一的方式访问标准容器和C风格数组。注意,和begin/end不同,这些新的函数不是定制点(customisation point),仅为方便起见提供。(←_←大概是说这些函数不会自动调用,不像begin/end会在range-based for中自动调用?)

文件:P0025R0
概要:clamp
注释:clamp(x, low, high)在x处于区间[low, high]内时返回x,否则返回最接近的边界(小于low就返回low,大于high就返回high)。

文件:P0295R0
概要:gcd和lcm
注释:数论中的函数,计算两个整数的最大公约数和最小公倍数。

文件:N4508
概要:shared_mutex类
注释:读-写互斥体,能够以共享或独占模式锁定。

文件:P0154R1
概要:干扰尺寸(interference sizes)
注释:两个新的实现定义常量hardware_constructive_interference_size、hardware_destructive_interference_size允许平台记录其高速缓存行大小(cache line size),以便用户避免虚假共享(破坏性干扰)并提高局部性(建设性干扰)。定义了两个单独的常量,以支持异构体系结构(heterogeneous architecture)。

文件:P0220R1
概要:元组(tuple)apply
注释:调用一个callable,参数从给定的元组中提取。

文件:P0209R2
概要:从元组构造
注释:新的函数模板make_from_tuple,功能是用给定元组的成员初始化T类型的值。它有点像刚提到的apply,但是它用于构造函数。

文件:P0005R4P0358R1
概要:通用否定函数对象not_fn
注释:一个调用包装器(call wrapper),对它包装的callable取否定。它适用于具有任意数量参数的callable。它取代了旧的not1和not2包装器。

文件:P0220R1
概要:memory resource
注释:一套新的组件,包括用于动态选择内存提供者的memory resource基类,以及三个具体实现(synchronized_pool_resource、unsynchronized_pool_resource、monotonic_buffer_resource)。有关用例参见下一条。

文件:P0220R1P0337R0
概要:多态的内存分配器
注释:使用memory resource的内存分配器,memory resource可以在运行时改变,不是分配器类型的一部分。为方便使用,还提供了类型别名,如std::pmr::vector<T> = std::vector<T, polymorphic_allocator<T>>。

文件:P0220R1P0253R1
概要:搜索器函数对象
注释:使用Boyer-Moore和Boyer-Moore-Horspool算法、用于搜索子字符串的函数对象,和一个使用这些函数对象的algorithm。

对现有特性的修改

文件:N3928
概要:只接受一个参数的static_assert
注释:static_assert声明不再需要第二个参数:static_assert(N > 0);

文件:N4230
概要:嵌套命名空间声明
注释:namespace foo::bar { /* ... */ }

文件:N4051
概要:允许用typename声明模板的模板参数(template template parameters)
注释:template <template <typename> typename Tmpl> struct X; 以前,模板的模板参数的声明只能用关键字class。

文件:P0184R0
概要:基于范围的for循环(range-based for)接受不同的begin/end类型
注释:for (decl : expr) 的规则受到重写,从以前的auto __begin = begin-expr, __end = end-expr;改成现在使用的 auto __begin = begin-expr; auto __end = end-expr;。这使得基于范围的for循环为新的Ranges(工作正在进行中)做好准备。

文件: P0195R2
概要:using-declaration 中的包扩展(pack expansion)
注释:template <typename ...Args> struct X : Args... { using Args::f...; };

文件:P0138R2
概要:固定枚举(fixed enum)值的构造
注释:类型为固定枚举 E 的变量可以用 E e { 5 }; 的形式定义,不再需要更啰嗦的 E e { E(5) };。

文件:N3922
概要:从花括号列表中推导auto的新规则
注释:以前,auto a{1, 2, 3}, b{1}; 是允许的,两个变量的类型都是 initializer_list<int>。现在 auto a{1, 2, 3}; 是错误的(ill-formed),auto b{1}; 声明一个 int。注意,auto a = {1, 2, 3}, b = {1}; 保持不变,推导出 initializer_list<int> 。此改变预期作为C++14的缺陷解决方案(defect resolution)。

文件:P0017R1
概要:对聚合初始化(aggregate initialization)的扩展
注释:列表初始化(list initialization)现在可以用聚合初始化的方式初始化基类子对象(base subobject):对于聚合类型struct base { int a1, a2; }; struct derived : base { int b1; };,以下初始化现在是合法的:derived{{1, 2}, 3}、derived{{}, 3}。

文件:N4259
概要:uncaught_exceptions()
注释:函数uncaught_exception被弃用,新函数uncaught_exceptions返回计数而不是布尔值。以前的功能实际上是不能用的,N4152解释了细节。

文件:N4266
概要:命名空间(namespace)和枚举成员(enumerators)的属性(attributes)
注释:命名空间和枚举成员现在可以用属性标记。例如,这允许标记命名空间和枚举成员为弃用的(deprecated)。

文件:P0028R4
概要:属性命名空间(attribute namespace)不需要重复指定
注释:当重复使用某一个属性命名空间时,这简化了命名空间限定。

----------
文件:N4279
概要:改进了std::map和std::unordered_map的插入
注释:m.try_emplace(key, arg1, arg2, arg3) 在m中已经含有key的时候没有任何效果,否则插入一个从参数构造的新元素。这个接口(interface)保证即使实参绑定到右值引用(rvalue reference),如果没有发生插入,实参将不会被移动(not moved from)。

文件:P0084R2
概要:emplace的返回类型
注释:顺序容器(sequence container)的过去返回void的emplace、emplace_front、emplace_back成员函数模板,现在返回刚刚插入的元素的引用。(关联容器不受影响,因为它们的插入函数一直都返回相关元素的迭代器。)

文件: P0083R3P0508R0
概要:set和map的拼接(splice)
注释:一个叫做节点句柄node handle)的新机制加入了标准容器库。这个新机制允许在不同的map/set对象之间移植元素,而不会触及容器中的对象。此外,这个机制使得对提取(extract)出的键值进行可变访问成为可能。(←_←可以不使用mutable就修改set的元素和map的key了,当然在修改之前必须先提取出来,修改之后再插入回去,以保证set和map的内部结构不受破坏)

文件:P0272R1
概要:非const的string::data
注释:现在有了一个非const的basic_string::data重载,返回一个可变的(mutable)指针。此外,C++17允许向空终止符(null terminator)写入,只要写入的值是零。这使得字符串类更便于和C风格接口一起使用。

文件:P0156R0P0156R2
概要:变长(variadic)版的lock_guard,名叫scoped_lock
注释:增加了变长的类模板scoped_lock<Args...>。它可以同时给多个可锁的对象(lockable object)加锁(使用和std::lock相同的算法),并且在析构函数中释放锁。一开始建议直接修改lock_guard,使它成为变长模板,但是发现这会造成ABI不兼容,所以现在我们有了新的类模板scoped_lock,它严格优于旧的lock_guard,所以不要使用lock_guard了。

文件:P0006R0
概要:变量模板trait
注释:对于每一个只包含一个静态成员常量的trait foo ,有了一个对应的变量模板foo_v<Args...>。foo_v<Args...>等价于foo<Args...>::value。

文件:P0152R1
概要:atomic::is_always_lock_free
注释:一个新的静态成员常量is_always_lock_free记录了给定原子类型(atomic type)的操作是否总是无锁(lock-free)的。现有的非静态成员函数可能对同一原子类型的不同值给出不同的答案。

文件:P0220R1P0414R2
概要:数组的shared_ptr
注释:类模板shared_ptr现在可以支持C风格数组,只需传入T[]或T[N]作为模板参数。接受原始指针(raw pointer)的构造函数将会设置(install)合适的数组内存释放器(array deleter)。

文件:P0163R0
概要:shared_ptr::weak_type
注释:shared_ptr<T>现在有了一个成员类型weak_type,它是weak_ptr<T>的别名。这使得泛型代码不需要解构shard_ptr的类型就可以知道对应的weak_ptr类型。

文件:P0030R1
概要:三维的斜边长(hypotenuse)
注释:三维斜边长hypot(x, y, z) (可以用来计算三维空间中两个点的距离)作为额外的重载加入 <cmath> (但没有加入<math.h>,并且只定义在命名空间std中)。

文件:P0040R3
概要:更多用于未初始化内存的算法
注释:增加在未初始化的内存中构造对象和销毁对象的算法。包括分别进行默认初始化(default-initialization)和值初始化(value-initialization)的版本。

文件:N4510
概要:内存分配器对不完整类型(incomplete type)的支持
注释:这项改变放宽了内存分配器对它的值类型(value type)的要求,使得值类型可以是不完整类型。例如,这允许递归的结构,如:struct X { std::vector<X> data; };

文件:P0092R1P0505R0
概要:对<chrono>的改变
注释:为时间点(time point)增加了向上取整(floor)、向下取整(ceiling)、除法(division)和四舍六入五成双取整(rounding)。(←_←我们中出了一个除法。根据P0092R1来看,原文似有问题,增加的应该是绝对值abs而不是除法。)大多数成员函数成为constexpr函数。

文件:P0426R1
概要:char_traits的constexpr支持
注释:对于标准要求的所有的char_traits特化(也就是std::char_traits<char>、std::char_traits<wchar_t>、std::char_traits<char16_t>、std::char_traits<char32_t>这四个类),成员函数length、compare、find和assign现在都是constexpr成员函数了。这使得string view可以在常量表达式中更广泛地使用。

文件:N4387
概要:改善pair和tuple
注释:这个变化使得pair和tuple的构造函数变为“带条件的explicit”构造函数。只有在有某个元素的对应构造函数是explicit的时候,pair和tuple的构造函数才是explicit的。

文件:P0435R1P0548R1
概要:对common_type的改变
注释:因为如果不改一下common_type,就不能称为新标准了……

杂项

文件:P0063R3
概要:C++引用C11
注释:C++标准现在正式地(normatively)引用C11 (ISO/IEC 9899:2011) 为“C标准”(“The C Standard”)了。这不仅是应对ISO的要求(ISO要求对其他国际标准的引用必须引用最新发布的版本,而不是某个历史版本),也使得我们可以使用aligned_alloc,它对于改进动态内存管理(dynamic memory management)非常有用。

文件:P0180R2
概要:保留的(reserved)命名空间
注释:所有形如 stdN (其中 N 是一串数字)的命名空间现在被保留了。(用户不能使用了。)

文件:P0175R1
概要:C库总览(synopsis)
注释:一个纯粹非技术性(editorial)的变化:标准库的“C库”部分的所有头文件的内容现在在C++标准文档中以完整的总览(synopsis)的形式展现(也就是完整列出每个标准头文件包含的完整声明),而不再是像以前一样只列出名字。这使得与C语言中的语义不同的地方(例如增加的重载,针对language linkage的重载)更容易理解。

文件:N4262 P0134R0 P0391R0 N4284
概要:术语“forwarding reference”、“default member initializer”、“templated entity”、“contiguous iterator”(分别对应四个文件)
注释:这些变化没有正式的(normative)影响,但是它们为迄今为止只从语言规则中出现的概念建立了官方术语。精确和众所周知的术语简化了关于C++的讨论,也简化了规范。

未列入的文件

以下文件在委员会会议(committee meeting)上被接受,但是它们的内容过于专门,不能称为单独的特性:N4258N4268N4277N4285P0031R0P0074R0P0270R3P0283R2P0296R2P0503R0P0509R1P0516R0P0517R0P0558R1P0599R1P0612R0

以下文件包含被接受为缺陷报告的问题(issue)。处理CWG问题的文件有N4192N4457
P0164R0P0167R2P0263R1P0384R0P0398R0P0490R0P0507R0P0519R0P0520R0P0522R0P0575R1P0576R1P0613R0P0622R0。处理LWG问题的文件有N4245N4366N4383N4525P0165R0P0165R1P0165R2P0165R2P0165R3P0165R4P0304R1P0397R0P0610R0P0625R0。每份文件中只有部分问题被选出,具体细节见会议记录(meeting minutes)。以下单独的文件解决特定的问题:N4089N4261P0033R1P0136R1P0250R3P0418R2P0513R0。历年来解决的问题(accepted issue)的完整清单公布在CWG issues listLWG issues list,二者会经常更新。

以下文件包含没有出现在已发布的国际标准中,但被认为是C++17的缺陷的内容:P0710R1P0711R0P0727R0

缺陷、问题、bug修复

标准委员会维护了一份问题issue)清单(见上面的链接),其中包含了对改进正式表述(normative wording)的建议(澄清、简化、更正等)。当一个问题被接受,并且它影响了已发布的标准(不仅仅影响正在进行的新工作),我们就有了一个缺陷报告defect report)。C++的完整规范包括最新发布的标准文档和所有此后发布的用于修正标准文档的缺陷报告。缺陷报告的解决方案也会加入工作草案(如果适用的话),这样下一版标准就不会有这个问题了。

因此,对于“什么是合法的C++14程序”这样的问题,答案是随时间变化的,直到C++17发布为止。特别地,在编译器供应商提供了支持语言的特定版本的模式(例如-std=c++11、-std=c++17)的时候,情况就更复杂了。供应商可以认为缺陷解决方案适用于所有含有此缺陷的历史版本。(而ISO认为只有最新发布的版本才是唯一的标准。不管以前的版本有怎样的缺陷ISO都不会修正。)

一些文件在C++17发布之后才应用到工作草案上,但是旨在作为缺陷报告。这些文件在上面的变化总结中用“DR”标注。应用于C++17但是对更早(C++17以前)的内容产生影响的缺陷报告在此没有专门标出。应用于C++14并且解决方案包含在C++17之中的缺陷报告也没有标出。已解决问题的完整历史以及它们所适用的标准版本在CWG和LWG问题清单中隐含,但难以明确列出。

展示C++17的代码片段合集

std::unordered_map<std::string, std::unique_ptr<Foo>> items;
std::vector<std::unique_ptr<Foo>> standby;

// 如果没有'id'元素,则把'foo'作为'id'元素插入。
// 否则把'foo'保存起来以备之后使用,将其放入standby中。

// C++17以前

void f(std::string id, std::unique_ptr<Foo> foo) {
  auto it = items.find(id);
  if (it == items.end()) {
    auto p = items.emplace(std::move(id), std::move(foo));
    p.first->second->launch();
  } else {
    standby.push_back(std::move(foo));
    standby.back()->wait_for_notification();
  }

  // 注意:
  // * 变量'id'不能再次使用(moved-from);或者……
  // * ……需要使用'const string& id'作为参数,并强制复制。
  // * 做了两次map查找。有序的map可以使用lower_bound + hint,但无序的map(unordered map)用不了。
  // * (不能无条件调用emplace,因为它可能导致*foo被析构。)
}

// 使用C++17

void f(std::string_view id, std::unique_ptr<Foo> foo) {
  if (auto [pos, inserted] = items.try_emplace(id, std::move(foo)); inserted) {
    pos->second->launch();
  } else {
    standby.emplace_back(std::move(foo))->wait_for_notification();
  }
}

下面的代码片段展示了template <auto>的用法,例子中的类模板将一个非成员函数转发给绑定了类实例的成员函数,并且成员函数是这个类模板的类型的一部分。

// C++17以前

template <typename T, int (T::* MF)(int, int)> // 两个参数:一个类型参数,一个非类型参数
struct Delegate { /* ... */ };

int n = Delegate<MyComplexClass, &MyComplexClass::an_imporant_function>(&obj)(10, 20);

// 使用C++17

template <auto> struct Delegate;                  // 一个(非类型)参数
template <typename T, int (T::* MF)(int, int)>
struct Delegate<MF>  { /* ... */ };              // 实现方法和以前相同,但现在使用偏特化(partial specialization)

int n = Delegate<&MyComplexClass::an_imporant_function>(&obj)(10, 20);

下面的代码片段展示fold表达式在泛型代码中的用法。

// 对于参数包f中的所有元素,调用f(n)。
template <typename ...F>
void ApplyAll(int n, const F&... f) {
   (f(n), ...); // 一元fold (over the comma operator)
}

// 对于参数包f中的所有元素,计算f(a, b),返回和。
template <typename ...F>
int ApplyAndSum(int a, int b, const F&... f) {
   return (f(a, b) + ... + 0); // 二元fold
}

下面的代码片段展示shared_ptr对数组的支持。

// C++17以前
std::shared_ptr<char> p(new char[N], std::default_delete<char[]>()); // 如果不提供删除器(deleter)就错了

// 使用C++17
std::shared_ptr<char[]> p(new char[N]); // 删除器自动使用“delete[]”
33 条评论