在C++17中的部分新特性
C++17已经发布了有一些时候,并且很多的编译器已经完成了对C++17的支持,那对于C++17中的新特性,我也好奇的玩了一些,其中的几个新特性特别吸引我的注意。
1 if-init
1.1 Grammar
if ( init-statement condition ) init-statement 可以是:
- 表达式语句(可以是空语句,仅
;
) - 声明语句
1.2 Thinking
if-init 这个 feature 尝试着让我们的代码更可读和干净,并且可以帮助我们更好的控制对象的生命周期。在从前的 if
语句中,我们经常会做类似的事情:
|
|
这个叫做iter
的变量其实我们除了用于这个if
判断和语句块中的操作之外,我们可能再也不会用到它。它的生命周期其实无形的被延长到了整个函数的结束。就像我们在for
语句中做的一样,我们为什么不把这个对象的生命周期限定在一个if
语句块中呢?所以就有了 if-init 这个 feature。
|
|
这里的写法就像在for
语句中的那样自然,我们在一个if
中初始化一个对象,并且使用它,当离开if
语句的时候,这个对象就被销毁了。
一些更多的用法:
|
|
2 structured-bingds
2.1 Grammar
attr(optional) cv-auto ref-operator(optional) [ indentifier-list ] = expression ; attr(optional) cv-auto ref-operator(optional) [ indentifier-list ] { expression }; attr(optional) cv-auto ref-operator(optional) [ indentifier-list ] ( expression ); 这里在绑定时候的基本规则是:(这里用 Expression 来表示 expression 表达式的类型)
- 如果 Expression 是数组类型,则一次绑定到数组元素
- 如果 Expression 是非联合体类型,且
std::tuple_size<Expression>
是完整类型,则使用类tuple绑定 - 如果 Expression 是非联合体类型,且
std::tuple_size<Expression>
不是完整类型,则以此绑定到类的公开数据成员
2.1.1 Case 1:Binding an array
每个 indentifier-list 中的标识符绑定到数组的每一个元素,标识符数量必须和数组大小一致。
|
|
2.1.2 Case 2: Binding a tuple-like type
std::tuple_size<Expression>::value
必须是一个合法的编译时期常量,并且标识符的数量和std::tuple_size<Expression>::value
的值相等。
在 ref-operator 为&
或者&&
时:
对于每一个标识符,若其对应的初始化表达式是左值,则为左值引用;若为右值则为右值引用。
每个标识符对应的初始化表达式使用如下规则:
expression.get<i>()
,表达式包含了一个如此的声明get<i>(expression)
,仅按照 ADL1 规则查找对应的声明(在这种使用中,如果 expression 对象是一个左值,那么传入的参数也为左值;如果 expression 对象是一个右值,那么传入的对象也为右值)
|
|
2.1.3 Case 3: Binding to public data members
每个 Expression 的非静态公开成员变量都会被依次绑定,并且标识符个数要和非静态公开成员变量个数相等。 所以的非静态公开成员都应该是无歧义的,且不能有任何的匿名联合体成员。 标识符最后的类型的 CV限定符会合并其类成员变量的类型中的CV限定符和声明中的的CV限定符。
|
|
2.2 Thinking
structured-binding 带给我们在书写上方便的同时也带来了相应的更大的心智负担,它让我们不仅要关注一个变量的类型,我们还要关注它所指向的对象的类型。因为在这里创建的不是一个引用,也不是对象的拷贝,而是一个既存对象的别名。 比如下面这个例子:
|
|
如果我们如此明显的书写的话,我们都知道,返回一个 sub-object 是不能触发 RVO2 的。那么我们如果用了结构化绑定的方式之后呢?
|
|
很遗憾的是,这里依旧不能触发 RVO 的,因为这里的b
是一个对象的别名,既不是引用也不是什么别的,它依旧会被认为是一个 sub-object。
3 if-init with structured-bindings
将 if-init 和 structured-bindings 可以帮助我们在很多地方缩减我们的代码:
|
|
4 if-constexpr
if-constexpr 可以帮助我们简化原本很多需要用 SFINAE 来实现的代码,用来模板中使用哪一部分的实现。
4.1 Sample 1
4.1.1 Before C++17
|
|
4.1.2 After C++17
|
|
4.2 Sample 2
4.2.1 Before C++17
|
|
4.2.2 After C++17
|
|
4.3 Summary
if-constexpr 的出现我感觉不是为了解决什么特别的问题,而是为了简化我们的代码,让我们在写的时候可以更自然,更符合直觉。
5 fold expression
5.1 Grammar
( pack op … ) ( … op pack ) ( pack op … op init ) ( init op … op pack ) 上面四个分别对应了一元右折叠、一元左折叠、二元右折叠、二元左折叠。
5.2 Explain
上面的四种折叠,分别会被展开成:
|
|
在使用二元折叠的时候,注意两个运算符必须是一样的,并且 init 如果是一个表达式的话,优先级必须低于 op,如果一定要高于的话,可以用括号括起来。
5.3 Example
端序交换:
|
|
5.4 Thinking
折叠表达式是在参数包的展开上面的又一次进步和改进,弥补了原本的参数包展开不易计算的问题。
6 noexcept
noexcept 成为了类型系统的一部分,这也是C++17对以前代码的唯一影响,可能会导致以前的代码不能通过编译。
void f();
和 void f() noexcept;
不再被认为是同一个类型,所以可能要为从前的实现提供额外的模板,或者提供额外的重载,再或者可以在模板中使用C++17中的新特性指定模板类型推导(template type deduction guide)。
7 template type deduction
为了实例化一个类模板,我们必须清楚的知道每个模板的类型,并且显示的写出他们,但是很多时候这是不必要的。
7.1 Thinking
这个新特性看起来似乎并没有那么吸引我:在类成员定义的时候,我没法使用到这个特性,可是这是我使用模板类最多的地方;在其他的很多地方,我似乎又可以使用auto
来替代写出一个变量的类型来。
References: