C++ 新版本特性 - C++17

参考资料

特性概览

c++17_1

c++17_2

c++17_3

c++17_4

折叠表达式

C++17中引入了折叠表达式,主要是方便模板编程,分为左右折叠,下图为其解包形式:

  1. Unary right fold (E op ...) becomes (E1 op (... op (EN-1 op EN)))
  2. Unary left fold (... op E) becomes (((E1 op E2) op ...) op EN)
  3. Binary right fold (E op ... op I) becomes (E1 op (... op (EN−1 op (EN op I))))
  4. Binary left fold (I op ... op E) becomes ((((I op E1) op E2) op ...) op EN)
#include <iostream>
#include <vector>

template <typename... Args>
void printer(Args &&...args)
{
	(std::cout << ... << args) << '\n';
}

template <typename... Args>
auto sub_right(Args... args)
{
	return (args - ...);
}

template <typename... Args>
auto sub_left(Args... args)
{
	return (... - args);
}

template <typename... Args>
auto sum_right(Args... args)
{
	return (args + ...);
}

int main()
{
	printer(1, 2, 3, "abc");

	std::cout << sub_right(8, 4, 2) << std::endl; // (8 - (4 - 2)) = 6
	std::cout << sub_left(8, 4, 2) << std::endl;  // ((8 - 4) - 2) = 2

	std::cout << sum_right(std::string{"hello "}, std::string{"world"})
			  << std::endl; // hello world
	
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

运行结果:

[root@iZuf61kbf845xt6tz10abgZ c17]# g++ -std=c++17 fold_expressions.cpp -o fold_expressions
[root@iZuf61kbf845xt6tz10abgZ c17]# ./fold_expressions
123abc
6
2
hello world
1
2
3
4
5

类模板参数推导

类模板实例化时,可以不必显式指定类型,前提是保证类型可以推导:

#include <utility>
#include <tuple>
#include <iostream>
#include <functional>
#include <algorithm>

template <typename T>
class A
{
public:
	A(T, T){};
};

int main()
{
	std::pair p(2, 4.5);	 // std::pair<int, double> p
	std::tuple t(4, 3, 2.5); // std::tuple<int, int, double> t
	std::less l;			 // std::less<void> l
	auto y = new A{1, 2};	 // A<int>::A(int, int)

	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

auto占位的非类型模板形参

#include <iostream>

template <auto T>
void foo() { std::cout << T << std::endl; }

int main()
{
	foo<100>(); // foo<int>();
	//foo<8.8>(); // foo<double>(); 这个是错误的,待研究

	return 0;
}
1
2
3
4
5
6
7
8
9
10
11

编译期constexpr if语句/constexpr的lambda表达式

lambda表达式可以在编译期进行运算,且函数体不能包含汇编语句、goto语句、label、try块、静态变量、线程局部存储、没有初始化的普通变量,不能动态分配内存,不能有new delete等,不能为虚函数。

#include <iostream>

template <bool ok>
constexpr void foo()
{
	//在编译期进行判断,if和else语句不生成代码
	if constexpr (ok == true)
	{
		std::cout << "ok" << std::endl; //当ok为true时,下面的else块不生成汇编代码
	}
	else
	{
		std::cout << "not ok" << std::endl; //当ok为false时,上面的if块不生成汇编代码
	}
}

int main()
{
	foo<true>();  //输出ok,并且汇编代码中只有 std::cout << "ok" << std::endl;
	foo<false>(); //输出not ok,并且汇编代码中只有 std::cout << "not ok" << std::endl;

	constexpr auto foo = [](int a, int b)
	{ return a + b; };
	static_assert(6 == foo(2, 3), "compile-time judge"); //static_assert关键字,用来做编译期间的断言,因此叫做静态断言
	//compile_time_if_constexpr.cpp:24:18: error: static assertion failed: not compile-time
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

内联变量

扩展的inline用法,使得可以在头文件或者类内初始化静态成员变量:

// test.h
inline int val = 1;
// test.cpp
struct A
{
	inline static int val = 1;
};
1
2
3
4
5
6

结构化绑定

在C++11中,如果需要获取tuple中元素,需要使用get<>()函数或者tie<>函数,这个函数可以把tuple中的元素值转换为可以绑定到tie<>()左值的集合。 C++17中的结构化绑定,大大方便了类似操作,而且使用引用捕获时,还可以修改捕获对象里面的值,代码也会简洁很多。

#include <iostream>
#include <unordered_map>
#include <tuple>

void c11_fun()
{
	auto student = std::make_tuple(std::string{"YongDu"}, 26, std::string{"man"});
	std::string name;
	size_t age;
	std::string gender;
	std::tie(name, age, gender) = student;
	std::cout << name << ", " << age << ", " << gender << std::endl;
}

struct Student
{
	std::string name;
	size_t age;
};

Student getStudent() { return {"dycc", 26}; }

void c17_fun()
{
	auto student = std::make_tuple(std::string{"YongDu"}, 26, std::string{"man"});
	auto [name, age, gender] = student;
	std::cout << name << ", " << age << ", " << gender << std::endl;
	// YongDu, 26, man

	std::unordered_map<std::string, size_t> students;
	students.emplace(std::make_pair("DuYong", 26));
	students.emplace(std::make_pair("YongDu", 26));

	for (auto &[name, age] : students)
	{
		std::cout << name << ", " << age << std::endl;
	}

	auto [_name, _age] = getStudent();
	std::cout << _name << ", " << _age << std::endl;
}

int main()
{
	c11_fun();
	c17_fun();

	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

执行结果:

[root@iZuf61kbf845xt6tz10abgZ c17]# ./structured_bindings
YongDu, 26, man
YongDu, 26, man
YongDu, 26
DuYong, 26
dycc, 26
1
2
3
4
5

if,switch初始化

#include <iostream>
#include <unordered_map>
#include <tuple>

void c11_fun()
{

	std::unordered_map<std::string, int> students{{"liBai", 18}, {"hanXin", 19}};
	auto iter = students.find("hanXin");
	if (iter != students.end())
	{
		std::cout << iter->second << std::endl;
	}
}

void c17_fun()
{
	std::unordered_map<std::string, int> students{{"liBai", 18}, {"hanXin", 19}};
	if (auto iter = students.find("hanXin"); iter != students.end())
	{
		std::cout << iter->second << std::endl;
	}
}

int main()
{
	//c11_fun();
	c17_fun();

	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

简化的嵌套命名空间

// C++17之前
namespace A {
namespace B {
namespace C {
void foo() {}
} // namespace C
} // namespace B
} // namespace A

// C++17
namespace A::B::C {
void foo() {}
} // namespace A::B::C
1
2
3
4
5
6
7
8
9
10
11
12
13

using声明语句可以声明多个名称

using std::cout, std::endl;
1

新的求值顺序规则

#include <iostream>
#include <unordered_map>
#include <cstdio>
int a() { return std::puts("a"); }
int b() { return std::puts("b"); }
int c() { return std::puts("c"); }
void z(int, int, int) {}

int main()
{
	std::unordered_map<int, int> m_map;
	m_map[0] = m_map.size(); // 此处不确定插入{0, 0},还是{0, 1}
	std::cout << "m_map[0]:" << m_map[0] << std::endl;
	
	z(a(), b(), c());       // all 6 permutations of output are allowed
    return a() + b() + c(); // all 6 permutations of output are allowed
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

执行结果:

m_map[0]:0
c
b
a
a
b
c
1
2
3
4
5
6

为了解决类似问题,C++17优化了求值顺序:

  • 后缀表达式从左到右求值,包括函数调用和成员选择表达式;
  • 赋值表达式从右向左求值,包括复合赋值;
  • 从左到右计算移位操作符的操作数。

新增属性

https://blog.csdn.net/weixin_42482896/article/details/118943564

还有疑问

  • [[fallthrough]]: switch语句中跳到下一条语句,不需要break,让编译器忽略告警;
  • [[nodiscard]]: 所修饰的内容不可被忽略,主要用于修饰函数返回值:
  • [[maybe_unused]]
#include <iostream>
#include <unordered_map>
#include <tuple>

void fallthrough_fun()
{

	int i = 1;
	int result;
	switch (i)
	{
	case 0:
		result = 1; // warning
		std::cout<< "result=1"<<std::endl;
	case 1:
		result = 2;
		[[fallthrough]]; // no warning
		std::cout<< "result=2"<<std::endl;
	default:
		result = 0;
		std::cout<< "result=0"<<std::endl;
		break;
	}
}

[[nodiscard]] auto nodiscard_fun(int a, int b) { return a + b; }

int main()
{
	fallthrough_fun();
	auto ret=nodiscard_fun(2, 3); // 放弃具有 "nodiscard" 属性的函数的返回值
	std::cout<< ret<<std::endl;
	[[maybe_unused]] int y = 2;
	std::cout<< y<<std::endl;
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

执行结果:

[root@iZuf61kbf845xt6tz10abgZ c17]# ./new_attributes
result=2
result=0
5
2
1
2
3
4
5

库相关

  • std::variant
    • C++17中提供了std::variant类型,意为多变的,可变的类型,类似于加强版的union,里面可以存放复合数据类型,且操作元素更为方便。
  • std::optional
    • 该类型主要用于简化函数返回值的判断
  • std::any
    • 在C++11中引入的auto自动推导类型变量大大方便了编程,但是auto变量一旦声明,该变量类型不可再改变。C++17中引入了std::any类型,该类型变量可以存储任何类型的值,也可以时刻改变它的类型,类似于python中的变量。
  • tuple
    • std::apply
      • 将tuple元组解包,并作为函数的传入参数。
    • std::make_from_tuple
      • 解包tuple作为构造函数参数构造对象。
    • std::apply
      • 将tuple元组解包,并作为函数的传入参数。
  • std::as_const
    • 将左值转化为const类型
  • std::shared_mutex
    • 读写锁相关
#include <iostream>
#include <unordered_map>
#include <tuple>
#include <optional>
#include <string>
#include <variant>
#include <any>

void variant_fun()
{
	std::cout << "variant_fun" << std::endl;
	std::variant<int, std::string> var("hello");
	std::cout << var.index() << std::endl;

	var = 123;
	std::cout << var.index() << std::endl;

	try
	{
		var = "world";
		std::string str = std::get<std::string>(var); // 通过类型获取
		var = 3;
		int i = std::get<0>(var); // 通过索引获取
		std::cout << str << ", " << i << std::endl;
	}
	catch (...)
	{
	}
}

std::optional<int> StoI(const std::string &str)
{
	try
	{
		return std::stoi(str);
	}
	catch (...)
	{
		return std::nullopt;
	}
}

void optional_fun()
{
	std::cout << "optional_fun" << std::endl;

	std::string str{"1234"};
	std::optional<int> result = StoI(str);
	if (result)
	{
		std::cout << *result << std::endl;
	}
	else
	{
		std::cout << "StoI() error." << std::endl;
	}
}

void any_fun()
{
	std::cout << "any_fun" << std::endl;

	std::any a = 1;
	std::cout << a.type().name() << ", " << std::any_cast<int>(a) << std::endl;

	a = 2.2f;
	std::cout << a.type().name() << ", " << std::any_cast<float>(a) << std::endl;

	if (a.has_value())
	{
		std::cout << a.type().name() << std::endl;
	}
	a.reset();
	if (a.has_value())
	{
		std::cout << a.type().name()<< std::endl;
	}
	a = std::string("hello");
	std::cout << a.type().name() << ", " << std::any_cast<std::string>(a) << std::endl;
}

class Test
{
public:
	Test(std::string name, size_t age) : _name(name), _age(age) { std::cout << "name: " << _name << ", age: " << _age << std::endl; }

private:
	std::string _name;
	size_t _age;
};

int add(int a, int b) { return a + b; }

void tuple_fun()
{
	std::cout << "tuple_fun" << std::endl;

	//创建元组
	std::tuple<int, char, double> tp(2, 'b', 8.5);
	auto data0 = std::get<0>(tp); //获得里面的元素
	auto data1 = std::get<1>(tp);
	auto data2 = std::get<2>(tp);
	auto tup1 = std::make_tuple("hello", 'a', 1.3);

	//解包tuple作为构造函数参数构造对象
	auto param = std::make_tuple("Jason", 25);
	std::make_from_tuple<Test>(std::move(param));

	// std::apply,将tuple元组解包,并作为函数的传入参数
	auto add_lambda = [](auto a, auto b, auto c)
	{ return a + b + c; };

	std::cout << std::apply(add, std::pair(2, 3)) << std::endl;
	std::cout << std::apply(add_lambda, std::tuple(2, 3, 4)) << std::endl;
}

void as_const_fun()
{
	std::cout << "as_const_fun" << std::endl;

	std::string str{"hello world"};
	std::cout << std::is_const<decltype(str)>::value << std::endl;

	const std::string str_const = std::as_const(str);
	std::cout << std::is_const<decltype(str_const)>::value << std::endl;
}

int main()
{
	variant_fun();
	optional_fun();
	any_fun();
	tuple_fun();
	as_const_fun();

	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136

执行结果:

[root@iZuf61kbf845xt6tz10abgZ c17]# ./std_fun                            
variant_fun
1
0
world, 3
optional_fun
1234
any_fun
i, 1
f, 2.2
f
next
NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE, hello
tuple_fun
name: Jason, age: 25
5
9
as_const_fun
0
1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21