C++17中那些值得关注的特性(上)
C++17标准在2017上半年已经讨论确定,正在形成ISO标准文档,今年晚些时候会正式发布。本文将介绍最新标准中值得开发者关注的新特新和基本用法。
总的来说C++17相比C++11的新特性来说新特性不算多,做了一些小幅改进。C++17增加了数十项新特性,值得关注的特性大概有下面这些:
- constexpr if
- constexpr lambda
- fold expression
- void_t
- structured binding
- std::apply, std::invoke
- string_view
- parallel STL
- inline variable
剩下的有一些来自于boost库,比如variant,any、optional和filesystem等特性,string_view其实在boost里也有。还有一些是语法糖,比如if init、deduction guide、guaranteed copy Elision、template、nested namespace、single param static_assert等特性。我接下来会介绍C++17主要的一些特性,介绍它们的基本用法和作用,让读者对C++17的新特性有一个基本的了解。
fold expression
C++11增加了一个新特性可变模版参数(variadic template),它可以接受任意个模版参数在参数包中,参数包是三个点…,它不能直接展开,需要通过一些特殊的方法才能展开,导致在使用的时候有点难度。现在C++17解决了这个问题,让参数包的展开变得容易了,Fold expression就是方便展开参数包的。
fold expression的语义
fold expression有4种语义:
- unary right fold (pack op …)
- unary left fold (… op pack)
- binary right fold (pack op … op init)
- binary left fold (init op … op pack)
其中pack代表变参,比如args,op代表操作符,fold expression支持32种操作符:
+ - * / % ^ & | = < > << >> += -= *= /= %= ^= &= |= <<= >>= == != <= >= && || , .* ->*
unary right fold的含义
fold (E op …) 意味着 E1 op (… op (EN-1 op EN)).
顾名思义,从右边开始fold,看它是left fold还是right fold我们可以根据参数包…所在的位置来判断,当参数包…在操作符右边的时候就是right fold,在左边的时候就是left fold。我们来看一个具体的例子:
template<typename... Args>
auto add_val(Args&&... args) {
return (args + ...);
}
auto t = add_val(1,2,3,4); //10
right fold的过程是这样的:(1+(2+(3+4))),从右边开始fold。
unary left fold的含义
fold (… op E) 意味着 ((E1 op E2) op …) op EN。
对于+这种满足交换律的操作符来说left fold和right fold是一样的,比如上面的例子你也可以写成left fold。
template<typename... Args>
auto add_val(Args&&... args) {
return (... + args);
}
auto t = add_val(1,2,3,4); //10
对于不满足交换律的操作符来说就要注意了,比如减法。
template<typename... Args>
auto sub_val_right(Args&&... args) {
return (args - ...);
}
template<typename... Args>
auto sub_val_left(Args&&... args) {
return (... - args);
}
auto t = sub_val_right(2,3,4); //(2-(3-4)) = 3
auto t1 = sub_val_left(2,3,4); //((2-3)-4) = -5
这次right fold和left fold的结果就不一样。
binary fold的含义
Binary right fold (E op … op I) 意味着 E1 op (… op (EN-1 op (EN op I)))。
Binary left fold (I op … op E) 意味着 (((I op E1) op E2) op …) op E2。
其中E代表变参,比如args,op代表操作符,I代表一个初始变量。
二元fold的语义和一元fold的语义是相同的,看一个二元操作符的例子:
template<typename... Args>
auto sub_one_left(Args&&... args) {
return (1 - ... - args);
}
template<typename... Args>
auto sub_one_right(Args&&... args) {
return (args - ... - 1);
}
auto t = sub_one_left(2,3,4);// (((1-2)-3)-4) = -8
auto t1 = sub_one_right(2,3,4);//(2-(3-(4-1))) = 2
相信通过这个例子大家应该对C++17的fold expression有了基本的了解。
comma fold
在C++17之前,我们经常使用逗号表达式和std::initializer_list来将变参一个个传入一个函数。比如像下面这个例子:
template<typename T>
void print_arg(T t)
{
std::cout << t << std::endl;
}
template<typename... Args>
void print2(Args... args)
{
//int a[] = { (printarg(args), 0)... };
std::initializer_list<int>{(print_arg(args), 0)...};
}
这种写法比较繁琐,用fold expression就会变得很简单了。
template<typename... Args>
void print3(Args... args)
{
(print_arg(args), ...);
}
这是right fold,你也可以写成left fold,对于comma来说两种写法是一样的,参数都是从左至右传入print_arg函数。
template<typename... Args>
void print3(Args... args)
{
(..., print_arg(args));
}
你也可以通过binary fold这样写:
template<typename ...Args>
void printer(Args&&... args) {
(std::cout << ... << args) << '\n';
}
也许你会觉得能写成这样:
template<typename ...Args>
void printer(Args&&... args) {
(std::cout << args << ...) << '\n';
}
但这样写是不合法的,根据binary fold的语法,参数包…必须在操作符中间,因此上面的这种写法不符合语法要求。
借助comma fold我们可以简化代码,假如我们希望实现tuple的for_each算法,像这样:
for_each(std::make_tuple(2.5, 10, 'a'),[](auto e) { std::cout << e<< '\n'; });
这个for_each将会遍历tuple的元素并打印出来。在C++17之前我们如果要实现这个算法的话,需要借助逗号表达式和std::initializer_list来实现,类似于这样:
template <typename... Args, typename Func, std::size_t... Idx>
void for_each(const std::tuple& t, Func&& f, std::index_sequence<Idx...>) {
(void)std::initializer_list<int> { (f(std::get<Idx>(t)), void(), 0)...};
}
这样写比较繁琐不直观,现在借助fold expression我们可以简化代码了。
template <typename... Args, typename Func, std::size_t... Idx>
void for_each(const std::tuple<Args...>& t, Func&& f, std::index_sequence<Idx...>) {
(f(std::get<Idx>(t)), ...);
}
借助coma fold我们可以写很简洁的代码了。
constexpr if
constexpr标记一个表达式或一个函数的返回结果是编译期常量,它保证函数会在编译期执行。相比模版来说,实现编译期循环或递归,C++17中的constexpr if会让代码变得更简洁易懂。比如实现一个编译期整数加法:
template<int N>
constexpr int sum()
{
return N;
}
template <int N, int N2, int... Ns>
constexpr int sum()
{
return N + sum<N2, Ns...>();
}
C++17之前你可能需要像上面这样写,但是现在你可以写更简洁的代码了。
template <int N, int... Ns>
constexpr auto sum17()
{
if constexpr (sizeof...(Ns) == 0)
return N;
else
return N + sum17<Ns...>();
}
当然,你也可以用C++17的fold expression:
template<typename ...Args>
constexpr int sum(Args... args) {
return (0 + ... + args);
}
constexpr还可以用来消除enable_if了,对于讨厌写一长串enable_if的人来说会非常开心。比如我需要根据类型来选择函数的时候:
template<typename T>
std::enable_if_t<std::is_integral<T>::value, std::string> to_str(T t)
{
return std::to_string(t);
}
template<typename T>
std::enable_if_t<!std::is_integral<T>::value, std::string> to_str(T t)
{
return t;
}
经常不得不分开几个函数来写,还需要写长长的enable_if,比较繁琐,通过if constexpr可以消除enable_if了。
template<typename T>
auto to_str17(T t)
{
if constexpr(std::is_integral<T>::value)
return std::to_string(t);
else
return t;
}
constexpr if让C++的模版具备if-else if-else功能了,是不是很酷,C++程序员的好日子来了。
不过需要注意的是下面这种写法是有问题的。
template<typename T>
auto to_str17(T t)
{
if constexpr(std::is_integral<T>::value)
return std::to_string(t);
return t;
}
这个代码把else去掉了,当输入如果是非数字类型时代码可以编译过,以为if constexpr在模版实例化的时候会丢弃不满足条件的部分,因此函数体中的前两行代码将失效,只有最后一句有效。当输入的为数字的时候就会产生编译错误了,因为if constexpr满足条件了,这时候就会有两个return了,就会导致编译错误。
constexpr if还可以用来替换#ifdef宏,看下面的例子
enum class OS { Linux, Mac, Windows };
//Translate the macros to C++ at a single point in the application
#ifdef __linux__
constexpr OS the_os = OS::Linux;
#elif __APPLE__
constexpr OS the_os = OS::Mac;
#elif __WIN32
constexpr OS the_os = OS::Windows;
#endif
void do_something() {
//do something general
if constexpr (the_os == OS::Linux) {
//do something Linuxy
}
else if constexpr (the_os == OS::Mac) {
//do something Appley
}
else if constexpr (the_os == OS::Windows) {
//do something Windowsy
}
//do something general
}
//备注:这个例子摘自https://blog.tartanllama.xyz/c++/2016/12/12/if-constexpr/
代码变得更清爽了,再也不需要像以前一样写#ifdef那样难看的代码块了。
constexpr lambda
constexpr lambda其实很简单,它的意思就是可以在constexpr 函数中用lambda表达式了,这在C++17之前是不允许的。这样使用constexpr函数和普通函数没多大区别了,使用起来非常舒服。下面是constexpr lambda的例子:
template <typename I>
constexpr auto func(I i) {
//use a lambda in constexpr context
return [i](auto j){ return i + j; };
}
constexpr if和constexpr lambda是C++17提供的非常棒的特性,enjoy it.
string_view
string_view的基本用法
C++17中的string_view是一个char数据的视图或者说引用,它并不拥有该数据,是为了避免拷贝,因此使用string_view可以用来做性能优化。你应该用string_view来代替const char和const string了。string_view的方法和string类似,用法很简单:
const char* data = "test";
std::string_view str1(data, 4);
std::cout<<str1.length()<<'\n'; //4
if(data==str1)
std::cout<<"ok"<<'\n';
const std::string str2 = "test";
std::string_view str3(str2, str2.size());
构造string_view的时候用char*和长度来构造,这个长度可以自由确定,它表示string_view希望引用的字符串的长度。因为它只是引用其他字符串,所以它不会分配内存,不会像string那样容易产生临时变量。我们通过一个测试程序来看看string_view如何来帮我们优化性能的。
using namespace std::literals;
constexpr auto s = "it is a test"sv;
auto str = "it is a test"s;
constexpr int LEN = 1000000;
boost::timer t;
for (int i = 0; i < LEN; ++i) {
constexpr auto s1 = s.substr(3);
}
std::cout<<t.elapsed()<<'\n';
t.restart();
for (int i = 0; i < LEN; ++i) {
auto s2 = str.substr(3);
}
std::cout<<t.elapsed()<<'\n';
//output
0.004197
0.231505
我们可以通过字面量””sv来初始化string_view。string_view的substr和string的substr相比,快了50多倍,根本原因是它不会分配内存。
string_view的生命周期
由于string_vew并不拥有锁引用的字符串,所以它也不会去关注被引用字符串的生命周期,用户在使用的时候需要注意,不要将一个临时变量给一个string_view,那样会导致string_view引用的内容也失效。
std::string_view str_v;
{
std::string temp = "test";
str_v = {temp};
}
这样的代码是有问题的,因为出了作用域之后,string_view引用的内容已经失效了。
总结
本文介绍了C++17的fold expression、constexpr if、constexpr lambda和string_view。fold expression为了简化可变模板参数的展开,让可以模板参数的使用变得更简单直观;constexpr if让模板具备if-else功能,非常强大。它也避免了写冗长的enable_if代码,让代码变得简洁易懂了;string_view则是用来做性能优化的,应该用它来代替const char*和const string。
这些特性对之前的C++14和C++11做了改进和增强,非常酷,欢迎订阅《程序员》,后续系列文章会接着介绍其他C++17中值得关注的新特性。
C++17中那些值得关注的特性(上)的更多相关文章
- 标准库中最值得关注的两个 装饰器是 lru_cache 和全新的 singledispatch(Python 3.4 新增)
clock 装饰器 def clock(func): @functools.wraps(func) def clocked(*args, **kwargs): t0 = time.perf_count ...
- Hadoop 3.0.0-alpha1几个值得关注的特性
1.支持纠删码:意味着更灵活的存储策略,即经常使用的数据利用备份方式存储(3倍存储消耗),冷数据利用纠删码容错(1.4倍存储消耗,但会造成额外的IO及CPU消耗): 2.MapReduce任务支持本地 ...
- C++11 中值得关注的几大变化(网摘)
C++11 中值得关注的几大变化(详解) 原文出处:[陈皓 coolshell] 源文章来自前C++标准委员会的 Danny Kalev 的 The Biggest Changes in C++11 ...
- C# 8.0的三个值得关注的新特性
本文翻译自:https://dzone.com/articles/3-new-c-8-features-we-are-excited-about 转载请注明出自:葡萄城官网,葡萄城为开发者提供专业的开 ...
- Node 12 值得关注的新特性
前言 时隔一年,Node.js 12 如约而至,正式发布第一个 Current 版本. 该版本带来了诸如: V8 更新带来好多不错的特性. HTTP 解析速度提升. 启动速度大幅提升. 更好的诊断报告 ...
- Atitit python3.0 3.3 3.5 3.6 新特性 Python2.7新特性1Python 3_x 新特性1python3.4新特性1python3.5新特性1值得关注的新特性1Pyth
Atitit python3.0 3.3 3.5 3.6 新特性 Python2.7新特性1 Python 3_x 新特性1 python3.4新特性1 python3.5新特性1 值得关注的新特性1 ...
- SQL Server 2016最值得关注的10大新特性
全程加密技术(Always Encrypted) 全程加密技术(Always Encrypted)支持在SQL Server中保持数据加密,只有调用SQL Server的应用才能访问加密数据.该功能支 ...
- Java 17中对switch的模式匹配增强
还记得Java 16中的instanceof增强 吗? 通过下面这个例子再回忆一下: Map<String, Object> data = new HashMap<>(); d ...
- .NET开发人员值得关注的七个开源项目 .
NET开发人员值得关注的七个开源项目 软近几年在.NET社区开源项目方面投入了相当多的时间和资源,不禁让原本对峙的开源社区阵营大吃一惊,从微软.NET社区中的反应来看,微软.NET开发阵营对开源工具的 ...
随机推荐
- selenium3.0 远程模式
准备工作: 1. 安装chrome浏览器 2. 下载selnium-server-standalone-3.0.1.jar 步骤: 1. java -jar selnium-server-standa ...
- CSU 1642 Problem B[难][前缀和]
Description 已知两个正整数a和b,求在a与b之间(包含a和b)的所有整数的十进制表示中1出现的次数. Input 多组数据(不超过100000组),每组数据2个整数a,b.(1≤a,b≤1 ...
- 【Cocos2dx 3.x Lua】TileMap使用
1.编辑TileMap地图资源 2.Cocos2dx 3.x Lua中使用TileMap Link: http://codepad.org/P0nFP1Dx local TileMap=clas ...
- PR曲线 ROC曲线的 计算及绘制
在linear model中,我们对各个特征线性组合,得到linear score,然后确定一个threshold,linear score < threshold 判为负类,linear sc ...
- springcloud21---Config-bus实现配置自动刷新
Pivotal(毕威拓)有VMware和EMC成立的. RabbitMQ是由ERlang(爱立信开发的,面向并发的编程语言),安装RabbitMQ先要安装ERlang. package com.itm ...
- c++第二十五天
p129~p131: 1.赋值运算的左侧运算对象必须是一个可修改的左值. 2.赋值运算满足右结合律. 3.赋值运算的结果是它的左侧对象,并且是一个左值. 验证: #include<iostrea ...
- MySQL测试工具之-tpcc
首先安装tpcc 官网地址:https://github.com/Percona-Lab/tpcc-mysql [root@test3 src]# unzip tpcc-mysql-master.zi ...
- Java生成PDF之iTextPDF的使用
今天做财务方面相关数据的导出功能,需要导出PDF和Excel,在项目经理那里得知有一个叫iTextPDF的java框架导出PDF文件很好用,于是拿来玩儿玩儿. package com.smart.pr ...
- presto-cli通过hive查询hdfs
1. 启动hive metastore 2. 启动hive thrift接口 参考:http://www.cnblogs.com/kisf/p/7497261.html 3. 下载presto se ...
- 解决应用程序无法正常启动0xcxxxxxxxxxx问题
简述:使用VS2008写了一个MFC程序,结果传到别人的机子上(WIN7)出现应用程序正常初始化(0xc0150002)失败的问题.为什么我的机子上可以,而别人的机子上运行不了呢?下面是我找到的一个解 ...