C++17 详解 4
本文为 《C++17 in detail》 一书的中文渣中渣译文,不足之处还望指正。
1.2 修复(Fixes)
修复的定义是有争论的。下边 3 条应该算是对在之前规则下缺失、不能正常工作的内容的修复。
1.2.1 直接列表初始化上 auto
的新规则
C++11 起引入了一个奇怪的问题:
1 | auto x{ 1 }; |
被推断为 std::initializer_list<int>
。大多数情况下这个行为不是本意,你原本期望它像 int x{ 1 };
一样工作。花括号初始化是现代 C++11 里首推的初始化样式,但是这种异常让这个特性变弱了。
在新标准里,我们可以修复它,以使它被推断为 int
。
若要如此,我们需要理解两种初始化方式——拷贝(初始化)和直接(初始化):
1 | auto x = foo(); // 拷贝初始化 |
C++17 为直接初始化引入了两条新规则:
- 对只有一个元素的花括号初始化列表,
auto
推断会根据列表项推断; - 对包含多于一个元素的花括号初始化列表,
auto
推断是非良构的。
译注:非良构(ill-formed)代码即错误代码,会导致编译错误。
举几个例子:
1 | auto x1 = { 1, 2 }; // decltype(x1) 值为 std::initializer_list<int> |
扩展:
本修改提案:N3922、N3681。编译器很早就修复了这个问题,GCC 5.0 (2015 年中)、Clang 3.8(2016 年初)、MSVC 2015(2015 年中)里此项改进就已可得,远早于 C++17 被批准。
1.2.2 不带消息体的 static_assert
本特性新加了一个 static_assert
的重载版本,允许你只通过条件使用 static_assert
而不用传递消息参数。
它和其它的断言实现兼容,比如 BOOST_STATIC_ASSERT
。有 boost 经验的程序员可以很容易切换到 C++17 的 static_assert
。
1 | static_assert(std::is_arithmetic_v<T>, "T must be arithmetic"); |
大多数情况下,条件本身就已足够表达意图,无需在消息字符串中再提及。
扩展:本修改提案:N3928。
1.2.3 range-based for 循环中使用类型不同的 begin
和 end
C++11 引入了 range-based for 循环:
1 | for (for-range-declaration : for-range-initializer) |
根据标准,这种循环表达式等同于如下代码:
1 | { |
如你所见,__begin
和 __end
类型相同。这样能很好地工作,但是伸缩性不够。比如你可能会想一直迭代到某个跟范围起点类型不同的哨兵。
C++17 里它被改为:
1 | { |
__begin
和 __end
的类型不一样,只有比较操作符是必要的。这个改动对 for 的使用者没有任何可见的后果,但是对库的实现多了更多选择。比如,这个小改动允许 Range 技术规范(C++20 中的 Ranges
)可以和 range for 循环一起工作。
扩展:本修改提案:P0184R0。
1.3 编译器支持
略。
译注:懒得翻译了,到今天 C++17 已经被三大编译器厂商支持了 N 年了。