C++ 新版本特性 - C++17
参考资料
特性概览
折叠表达式
C++17中引入了折叠表达式,主要是方便模板编程,分为左右折叠,下图为其解包形式:
- Unary right fold (E op ...) becomes (E1 op (... op (EN-1 op EN)))
- Unary left fold (... op E) becomes (((E1 op E2) op ...) op EN)
- Binary right fold (E op ... op I) becomes (E1 op (... op (EN−1 op (EN op I))))
- 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;
}
运行结果:
详情
[root@iZuf61kbf845xt6tz10abgZ c17]# g++ -std=c++17 fold_expressions.cpp -o fold_expressions
[root@iZuf61kbf845xt6tz10abgZ c17]# ./fold_expressions
123abc
6
2
hello world
类模板参数推导
类模板实例化时,可以不必显式指定类型,前提是保证类型可以推导:
详情
#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;
}
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;
}
编译期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;
}
内联变量
扩展的inline用法,使得可以在头文件或者类内初始化静态成员变量:
详情
// test.h
inline int val = 1;
// test.cpp
struct A
{
inline static int val = 1;
};
结构化绑定
在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;
}
执行结果:
详情
<!-- @include: ./res/structured_bindings.txt
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;
}
简化的嵌套命名空间
详情
// 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
using声明语句可以声明多个名称
using std::cout, std::endl;
新的求值顺序规则
详情
#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
}
执行结果:
详情
<!-- @include: ./res/new_order_eva_rules.txt
为了解决类似问题,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;
}
执行结果:
[root@iZuf61kbf845xt6tz10abgZ c17]# ./new_attributes
result=2
result=0
5
2
库相关
- 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::apply
- 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;
}
执行结果:
[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