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
2
3
4
5
auto x = foo(); // 拷贝初始化
auto x{foo()}; // 直接初始化, 初始化为 initializer_list (until C++17)

int x = foo(); // 拷贝初始化
int x{foo()}; // 直接初始化

C++17 为直接初始化引入了两条新规则:

  • 对只有一个元素的花括号初始化列表,auto 推断会根据列表项推断;
  • 对包含多于一个元素的花括号初始化列表,auto 推断是非良构的。

译注:非良构(ill-formed)代码即错误代码,会导致编译错误。

举几个例子:

1
2
3
4
5
auto x1 = { 1, 2 }; // decltype(x1) 值为 std::initializer_list<int>
auto x2 = { 1, 2.0 }; // error: cannot deduce element type
auto x3{ 1, 2 }; // error: not a single element
auto x4 = { 3 }; // decltype(x4) 值为 std::initializer_list<int>
auto x5{ 3 }; // decltype(x5) 值为 int

扩展:

本修改提案:N3922N3681。编译器很早就修复了这个问题,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
2
static_assert(std::is_arithmetic_v<T>, "T must be arithmetic");
static_assert(std::is_arithmetic_v<T>); // C++17 起不再需要消息体

大多数情况下,条件本身就已足够表达意图,无需在消息字符串中再提及。

扩展:本修改提案:N3928

1.2.3 range-based for 循环中使用类型不同的 beginend

C++11 引入了 range-based for 循环:

1
2
3
4
for (for-range-declaration : for-range-initializer)
{
statement;
}

根据标准,这种循环表达式等同于如下代码:

1
2
3
4
5
6
7
8
9
{
auto && __range = for-range-initializer;
for ( auto __begin = begin-expr, __end = end-expr;
__begin != __end;
++__begin ) {
for-range-declaration = *__begin;
statement
}
}

如你所见,__begin__end 类型相同。这样能很好地工作,但是伸缩性不够。比如你可能会想一直迭代到某个跟范围起点类型不同的哨兵。

C++17 里它被改为:

1
2
3
4
5
6
7
8
9
{
auto && __range = for-range-initializer;
auto __begin = begin-expr;
auto __end = end-expr;
for ( ; __begin != __end; ++__begin ) {
for-range-declaration = *__begin;
statement
}
}

__begin__end 的类型不一样,只有比较操作符是必要的。这个改动对 for 的使用者没有任何可见的后果,但是对库的实现多了更多选择。比如,这个小改动允许 Range 技术规范(C++20 中的 Ranges)可以和 range for 循环一起工作。

扩展:本修改提案:P0184R0

1.3 编译器支持

略。

译注:懒得翻译了,到今天 C++17 已经被三大编译器厂商支持了 N 年了。

评论