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种语义:

  1. unary right fold (pack op …)
  2. unary left fold (… op pack)
  3. binary right fold (pack op … op init)
  4. 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。

如果你想学习C/C++可以来这个群,首先是330,中间是859,最后是766,里面可以学习和交流,也有资料可以下载。

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。

时间: 2024-08-18 06:33:39

C++17中那些值得关注的特性的相关文章

Android 5.0 Lollipop 新版本中那些值得关注的新功能

Android 5.0 Lollipop 新版本中那些值得关注的新功能 时间:2014-11-04 10:15 来源:PingWest 作者:光谱 Android,统治了全世界七成设备的操作系统--很可惜,并不是大一统,而是王子王孙群雄割据的状态--当然,今天我们讨论的不是统一和割据孰优孰劣的问题.除了美国进入冬令时之外,今天 Android 家族至少有一条重要消息值得关注:Google 自行开发的原生 Android 操作系统,也被誉为最纯净的 Android,将在美国时间 11 月 3 日正

IE浏览器中一个值得关注的JS问题

js|浏览器|问题 刚才在调试js,在mozilla中一切正常,但是在ie中却报告有configuration变量没有找到.以下是代码: var Environment = new (function Environment$ctor() { this.processCommandLine = function Env$procCmdLine(q) { var cmd = configuration {}; // <---- 就是这一行报告错误 // 其他代码省略 } }; 我先后把这一行改成如

2013上半年最值得关注的笔记本

  今年6月英特尔发布了Haswell,2013年的笔记本市场注定不会风平浪静的,在Haswell的激励下,厂商们纷纷借机推出全新设计的笔记本产品,品种繁多精品迭出是这一时期的特点.然而同时我们也注意到市场上除了全新设计的产品值得关注以外,上一代IVB平台的产品降价后实际上更具性价比,如果在这样一个时期购买笔记本,就需要同时将目光锁定在新老两个平台上面,最终根据自己的需求来选择. 那么哪些产品是今年上半年最值得关注的机型呢?今天就为大家来盘点一番. 要够买一款好的笔记本实际上最重要的是结合自己的

Vine Linux 6.3 新版本发布,新特性值得关注

Vine Linux 6.3 发布,此版本代号为 Malartic-Lagraviere,值得关注的新特性: update the software collection; update Linux kernel to 3.4.106 (latest LTS kernel 3.4.y);  bundle newer software - Firefox 33.0, Thunderbird 24.0, Sylpheed 3.4.2, LibreOffice 4.3.5, OpenSSL 1.0.1

C# 8.0的三个值得关注的新特性

C# 语言是在2000发布的,至今已正式发布了7个版本,每个版本都包含了许多令人兴奋的新特性和功能更新.同时,C# 每个版本的发布都与同时期的 Visual Studio 以及 .NET 运行时版本高度耦合,这也有助于开发者更好的学习掌握 C#,并将其与 Visual Studio 以及 .NET 的使用结合起来.   加快 C# 版本的发布速度 在被称为"新微软"的推动下,微软创新的步伐也加快了.为了做到加快步伐,微软开发部门将一些过去集成在一起的技术现在都分离了出来. Visual

小姚:电商圈比赛中值得关注的几个看点

2011年电商圈seo大赛开始到今天已经过去了14天,小姚通过搜索引擎和电商圈官方报名帖看到有很多的朋友都加入到了电商圈的大军中,比赛可以说是如火如荼,小姚昨天从排名第二的商才核心成员小何处了解到,每日从"电商圈"这个关键词来的流量大概是80+,小姚通过去年博百优大赛的数据对比来看,官方宣传和推广的还不到位,小姚10年见证了博百优seo大赛的全过程,今年有幸遇到电商圈seo比赛,耐不住手痒,也建立了博客,准备参加这次的电商圈比赛,参加的目的有2个,其一是看看自己的执行力,其二是本次大赛

“三网融合”中的投资机会值得关注

本报记者 易非 深圳报道 今年以来,随着新基金密集成立,招商基金投研团队认为,这些新基金或将重点关注" 三网融合"广电行业. 招商行业领先拟任基金经理涂冰云表示,中国电信运营商重组之后形成了各具特点的中国移动.中国电信和中国联通三大集团,竞争格局将有别于2G时代.此外,已提出多年的"三网融合"开始有了实质性进展,信号之一是中国电信推出的"CDMA+WiFi"营销方案,其二是深圳移动与深圳广电签署框架合作协议. 涂冰云认为,中国电信的"C

2015年最值得关注的十大H5营销案例

  2014年初,Html5广告(以下简称H5)开始在各平台崭露头角.以<特斯拉>为首的一批H5开始上线,在行业内引发了轰动.接着开始出现了一系列的H5游戏,例如<神经猫>.<寻找房祖名>等.在过去一年时间内H5经历了从全民兴起-爆发-沉淀-再爆发式增长的过程.但对于H5从业者来说不仅仅是技术方面的问题,对于营销和创意方面也存在一些不成熟的因素.那么H5从业者应该如何生存下去?什么样的H5能成为爆款?这是值得关注的问题. 2015年初,H5类型的广告在朋友圈突然就火了.

20个2013年最值得关注的网页设计趋势

在过去的一年中,我们都看到了网页设计趋势在日益增长.或许有些朋友还记得我早些时候的一些文章关于网页设计,有兴趣的可以点击来查看,现在我们可以看到其中许多想法已经实现了,甚至有些采用了一些更高层次的新奇想法.在今天这篇文章中,我将与大家分享2013年里20多种网页设计的全新趋势走向. 设计的影响仅仅是一个来自于我们的文化和用户界面感知的反馈.观念上这些趋势代表了在网页设计社区最受喜爱的点子.可是当提到设计团队时,就会想到设计师拥有他们独立的观点,所以要有保留的接受这些想法. 1.响应式布局 响应式