C++17部分特性整理
1、使 static_assert 的文本信息可选
- 简化和 static_assert的使用,c++17起,消息 可选
static_assert ( 布尔常量表达式 , 消息 ) (C++11 起)
static_assert ( 布尔常量表达式 ) (C++17 起)
2、删除 trigraphs
- 删除三元转移字符
- 最初因为iso646的标准,部分国家打不出# ~ ^ 之类的字符,使用??加一个字符的方式替代,c++11中不建议使用trigraphs,c++17中被弃用
Trigraph | Equivalent | |
---|---|---|
??= |
# |
|
??/ |
\ |
|
??' |
^ |
|
??( |
[ |
|
??) |
] |
|
??! |
` | ` |
??< |
{ |
|
??> |
} |
|
??- |
~ |
3、在模板参数中允许使用 typename(作为替代类)
- 在模板声明中,typename可用作class的代替品,以声明类型模板形参和模板模板形参(c++17起)
template<typename T> class my_array {};
// 两个类型模板形参和一个模板模板形参:
template<typename K, typename V, template<typename> typename C = my_array>
class Map
{
C<K> key;
C<V> value;
};
4、braced-init-list自动推导的新规则
- 采用大括号的形式初始化auto变量,可以自动完成类型的推导
- 规则braced-init-list 左侧只能是单个元素
auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int> 【initializer_list用于表示特定类型值的数组】
auto x2 = { 1, 2.0 }; // error: cannot deduce element type,会对右侧常量进行类型检查
auto x3{ 1, 2 }; // error: not a single element
auto x4 = { 3 }; // decltype(x4) is std::initializer_list<int>
auto x5{ 3 }; // decltype(x5) is int
-
std::initializer_list的简单介绍【列表初始化】
std::vector<int>a{1,2,3,4,5}; std::vector<int>a = {1,2,3,4,5}; //vecotr可以使用列表进行初始化,因为{1,2,3,4,5}被推导为std::initializer_list<int>类型,vector的构造函数对std::initializer_list<int>类型进行特化和对赋值运算符的重载进行了特化。
[图片上传失败...(image-bff33a-1584675555477)]
5、嵌套命名空间
- 嵌套命名空间定义:namespace A::B::C { ... } 等价于 namespace A { namespace B { namespace C { ... } } }。
- namespace A::B::inline C { ... } 等价于 namespace A::B { inline namespace C { ... } }。(c++20)inline 可出现于除首个以外的每个命名空间名之前
6、允许命名空间和枚举器的属性
1、格式
枚举格式:
enumerator: identifier attribute-specifier-seqopt
作用域格式:
- original-namespace-definition:
inlineopt namespace attribute-specifier-seqopt identifier { namespace-body }- extension-namespace-definition:
inlineopt namespace attribute-specifier-seqopt original-namespace-name { namespace-body }- unnamed-namespace-definition:
inlineopt namespace attribute-specifier-seqopt { namespace-body }
2、属性格式
[[attr]] [[attr1, attr2, attr3(args)]]
3、语法
- [[
属性列表
]] (c++11)- [[ using
属性命名空间
:属性列表
]] (c++17)
4、属性实例
(未找到有关namesapce和enum相关的实例,以函数属性列表的使用举例)
[[gnu::always_inline]] [[gnu::hot]] [[gnu::const]] [[nodiscard]]
inline int f(); // 声明 f 带四个属性
[[gnu::always_inline, gnu::const, gnu::hot, nodiscard]]
int f(); // 同上,但使用含有四个属性的单个属性说明符
// C++17:
[[using gnu : const, always_inline, hot]] [[nodiscard]]
int f[[gnu::always_inline]](); // 属性可出现于多个说明符中
int f() { return 0; }
int main() {}
7、新的标准属性:
[[fallthrough]]:
仅可应用到空语句以创建直落语句(fallthrough statement):[[fallthrough]];。
直落语句仅可用于 switch 语句中,其中待执行的下个语句是该 switch 语句的带 case 或 default 标号的语句。
-
指示从前一标号直落是有意的,而在发生直落时给出警告的编译器不应诊断它
void f(int n) { void g(), h(), i(); switch (n) { case 1: case 2: g(); [[fallthrough]]; case 3: // 直落时不警告 h(); case 4: // 编译器可在发生直落时警告【实际运行结果并未发生警告】 if(n < 3) { i(); [[fallthrough]]; // OK } else { return; } case 5: [[fallthrough]]; // 非良构,无后继的 case 或 default 标号 } }
[[nodiscard]]
- 出现在函数声明、枚举声明或类声明中
- 若从并非转型到 void 的弃值表达式中,调用声明为 nodiscard 的函数,或调用按值返回声明为 nodiscard 的枚举或类的函数,则鼓励编译器发布警告。
struct [[nodiscard]] error_info { };
error_info enable_missile_safety_mode();
void launch_missiles();
void test_missiles() {
enable_missile_safety_mode(); // 编译器可在舍弃 nodiscard 值时发布警告
launch_missiles();
}
error_info& foo();
void f1() {
foo(); // 并非按值返回 nodiscard 类型,无警告
}
[[maybe_unused]]
- 抑制针对未使用实体的警,此属性可出现在下列实体的声明中:
- 若编译器针对未使用实体发布警告,则对于任何声明为
maybe_unused
的实体抑制该警告。
[[maybe_unused]] void f([[maybe_unused]] bool thing1,
[[maybe_unused]] bool thing2)
{
[[maybe_unused]] bool b = thing1 && thing2;
assert(b); // 发行模式中,assert 在编译中被去掉,因而未使用 b
// 无警告,因为它被声明为 [[maybe_unused]]
} // 未使用参数 thing1 与 thing2,无警告
9、允许所有非类型模板实参的常量求值
- 附:实例
//队列某个元素或者非静态数据成员的地址,对于非类型模板参数是不合法的;
template<int* p> class X { };
int a[10];
struct S { int m; static int s; } s;
X<&a[2]> x3; // error: address of array element
X<&s.m> x4; // error: address of non-static member
X<&s.s> x5; // error: &S::s must be used OK: address of static member
X<&S::s> x6; // OK: address of static member
10、折叠表达式及在可变长参数模板中的使用
折叠表达式
格式
- 一元左折叠
(pack op ...)
- 一元右折叠
(... op pack)
- 二元右折叠
(pack op ... op I)
- 二元左折叠
(I op ... op pack)
展开
- 一元左折叠展开:
((E1 op E2) op ...) op EN
- 一元右折叠展开:
E1 op (... op (EN-1 op EN))
- 二元右折叠展开:
E1 op (... op (EN-1 op (EN op I)))
- 二元左折叠展开:
(((I op E1) op E2) op ...) op EN
注意
- 若用作 init 或 pack 的表达式在顶层优先级低于转型,则它可以加括号:
template<typename ...Args>
int sum(Args&&... args) {
// return (args + ... + 1 * 2); // 错误:优先级低于转型的运算符
return (args + ... + (1 * 2)); // OK
}
- 二元折叠表达式两边的操作数只能有一个未展开的的参数包
template<typename... Args>
bool f(Args... args) {
return (true + ... + args); // OK
}
template<typename... Args>
bool f(Args... args) {
return (args && ... && args); // error: both operands contain unexpanded parameter packs
}
变长参数模板参数解包方法
之前解包方法:
# include <iostream >
template < typename T0 >
void printf (T0 value ) {
std :: cout << value << std :: endl ;
}
template < typename T, typename ... Args >
void printf (T value , Args ... args ) {
std :: cout << value << std :: endl ;
printf ( args ...) ;
}
int main () {
printf (1, 2, "123 ", 1.1) ;
return 0;
}
基于constexpr的解包方法[c++17]
template < typename T0, typename ... T>
void magic(T0 t0, T... t) {
std::cout << t0 << std::endl;
if constexpr (sizeof ...(t) > 0) magic(t ...);
}
11、if constexpr关键字的使用
C++11 引入了constexpr 关键字,它将表达式或函数编译为常量;
-
C++17 将constexpr 这个关键字引入到if 语句中,允许在代码中声明常量,把这一特性引入到条件判断中去,让代码在编译时就完成分支判断;
template < typename T> auto print_type_info ( const T& t) { if constexpr (std :: is_integral <T >:: value ) { return t + 1; } else { return t + 0.001; } } int main () { std :: cout << print_type_info (5) << std :: endl ; std :: cout << print_type_info (3.14) << std :: endl ; }
编译时展现形式:
int print_type_info ( const int & t) { return t + 1; } double print_type_info ( const double & t) { return t + 0.001; }
12、结构化绑定声明
1、格式
- attr(可选) cv-auto ref-运算符(可选) [ 标识符列表 ] = 表达式 ;
- attr(可选) cv-auto ref-运算符(可选) [ 标识符列表 ] {表达式 };
- attr(可选) cv-auto ref-运算符(可选) [ 标识符列表 ] (表达式 );
名称 | 解释 |
---|---|
attr | 任意数量的属性的序列 |
cv-auto | 可有 cv 限定的 auto 类型说明符 |
ref-运算符 | & 或 && 之一 |
标识符列表 | 此声明所引入的各标识符的逗号分隔的列表 |
表达式 | 顶层没有逗号运算符的表达式(文法上为赋值表达式),且具有数组或非联合类之一的类型。 |
2、绑定情况
2.1绑定到数组
int a[2] = {1,2};
auto [x,y] = a; // 创建 e[2],复制 a 到 e,然后 x 指代 e[0],y 指代 e[1]
auto& [xr, yr] = a; // xr 指代 a[0],yr 指代 a[1]
2.2绑定到元组式类型
float x{};
char y{};
int z{};
std::tuple<float&,char&&,int> tpl(x,std::move(y),z);
const auto& [a,b,c] = tpl;
// a 指名指代 x 的结构化绑定;decltype(a) 为 float&
// b 指名指代 y 的结构化绑定;decltype(b) 为 char&&
// c 指名指代 tpl 的第 3 元素的结构化绑定;decltype(c) 为 const int
2.3绑定到数组成员
struct S {
int x1 : 2;
volatile double y1;
};
S f();
auto [x, y] = f(); // x 是标识 2 位位域的 int 左值
// y 是 volatile double 左值
//根据测试结果 auto [x, y]前不能加cv操作符,否则编译不过:由于tupule_size无法识别
3、注意示项
1、标志符的数量必须等于数组元素或元组元素或者结构体中非静态数据成员的数量;
13、if 和 switch 语句中的变量初始化
-
在if和switch中可以完成初始化,
-
if (
init-statement <u>opt</u> condition)
statement -
if (
init-statement <u>opt</u> condition)
statementelse
statement -
switch (
init-statement <u>opt</u> condition)
statement
-
表达式展开格式如下:
- if ( init-statement condition ) statement 展开格式如下:
{
init-statement
if ( condition ) statement
}
- if ( init-statement condition ) statement else *statemen 展开格式如下:
{
init-statement
if ( condition ) statement else statement
}
- switch ( init-statement condition ) statement 展开格式如下:
{
*init-statement*
switch ( condition ) statement
}
实例
实例1
before
status_code foo() {
{
status_code c = bar();
if (c != SUCCESS) {
return c;
}
}
// ...
}
after
status_code foo() {
if (status_code c = bar();c != SUCCESS) {
return c;
}
// ...
}
实例2
before
void safe_init() {
{
std::lock_guard<std::mutex> lk(mx_);
if (v.empty()){
v.push_back(kInitialValue);
}
}
// ...
}
after
void safe_init() {
if (std::lock_guard<std::mutex> lk(mx_);v.empty()){
v.push_back(kInitialValue);
}
// ...
}