db2 参数标识符使用无效_C++20 新特性(14):lambda初始化捕捉支持参数包

C++20 新增支持在lambda表达式中,捕捉上下文使用初始化捕捉时,允许使用参数包方式进行初始化。这个说明的确比较拗口,我们先了解一下参数包和 lambda 的初始化捕捉的基本情况,再来看这个改动具体是做什么的。

3627767107a2ee4e207aba4fc310a2f1.png

参数包(parameter pack )

参数包是在定义模板时,支持可变数量的模板参数的一种技术,支持类模板和函数模板的可变参数,

template< typename ... VT > class Tuple { };Tuple<> t1;      // <1> 不定数量类型参数,可以是0个类型参数Tuple t2;    // <2> 可以是一个类型参数,int型Tuple t3;   // <3> 可以是两个类型参数,int型和float型template< typename ... VT > void func( VT ... args );func( );             // <1> 相当于使用 void func( void );func( 1 );          // <2> 相当于使用 void func( int a1 );func( 2, 1.0 );   // <3> 相当于使用 void func( int a1, double a2 );

在使用参数包定义的变量时,常用的处理方式是直接参数包展开,或者使用展开表达式(fold expression):

template< typename ... VT, typename U > U sum( VT ... args ){    // <1> 如果有三个参数a1/a2/a3,展开后相当于: ( ( a1 + a2 ) + a3 )    U  u1 = ( ... + args );      return u1;}

lambda 的初始化捕捉

lambda在捕捉上下文变量时,可以有两种捕捉方式:

  1. simple-capture,简单捕捉,直接按值或者按引用的方式进行捕捉
  2. init-capture,初始化捕捉,定义一个局部变量,并且进行初始化之后进行捕捉
int x1 = 4;int x2 = 5;auto y = [ &r = x1, x1 = x1+1, x2 ] () -> int {   // <1> x2 是简单捕捉,r 和 x1 是初始化捕捉,    // <2> 注意上一行中的三个 x1 不是同一个对象,    // <3> 第一和第三个 x1 是引用外面的 x1 ,而第二个 x1 是定义内部使用的 x1 ,    r += 2;   // <4> r 是引用外部的 x1 ,所以对 r 进行修改相当于对外部的 x1 进行修改    return x1 + x2 +2;   // <5> 这里的 x1 是内部的 x1 ,初始化取值是 5 (即 4 + 1 ),不受上一行修改外部 x1 所影响} ();  // <6> 定义一个lambda之后马上调用,执行完之后,外部 x1 的值为 6 ,y 的值为 12

lambda 初始化捕捉支持参数包

早期 C++ 标准中的 lambda 初始化捕捉,不支持参数包,主要是因为早期的标准中规定, lambda 的初始化捕捉时,定义的每一个变量,都需要在相关联的闭包类中,有相应标识符名称的非静态成员变量。

但是如果一个未展开的参数包可以是一个有名称的变量,那么会增加很多复杂性,外部无法判断一个变量是否是可以展开的参数包,也很难将参数包控制在模板内部,因此早期 C++ 中禁止这种写法。

后来 C++ 标准中进行了改进, lambda 的捕捉初始化时定义的每一个变量,不再要求在相应闭包类中有相应标识符名称的非静态成员变量,而是改为无名字的成员变量,这样这个限制就不再存在了,因此 C++20 中就允许 lambda 初始化捕捉支持参数包。

#include #include #include #include using std::cout, std::endl, std::string;//  <1> 定义通用模板,使用static_cast<>方式转换返回int值template< typename T >int GetVal( T v ) { return static_cast( v ); } // <2> 定义字符串特化模板,将字符串转为int值template<>int GetVal( const char * v ) { return std::atoi( v ); } // <3> 定义字符串特化模板,将字符串转为int值template<>int GetVal( string v )   {    if( v.empty() )        return 0;    return std::stoi( v );}// <4> 传统的 C 语言方式的可变参数,运行时获取可变参数信息int sum_int( int num, ... )  {    std::va_list va;    va_start( va, num );    int i = 0;    int sum = 0;    for( i = 0; i < num; ++i )        sum += va_arg( va, int );    return sum;} // <5> C++模板的参数包方式的可变参数,编译时展开可变参数信息template< typename ... VT > int Sum( VT ... args )   {     // <6> 直接使用展开表达式    int a1 = ( ... + GetVal( args ) );       // <7> lambda中通过简单捕捉方式,按值使用可变参数    int a2 = [ args ... ] { return ( ... + GetVal( args ) ); } ();        // <8> C++17不支持,C++20才支持,    // lambda中通过捕捉初始化,按移动的方式使用可变参数    int a3 = [ ... newargs = std::move( args ) ] {           // <9> 只是展示如何获取参数个数和简单展开包拓展,        //  将编译期能处理的转为运行期再处理,未能发挥模板编译期处理的效率优势        return sum_int( sizeof ... ( newargs ), GetVal( newargs ) ... );      } ();      // <10> 注意上面使用 std::move() 之后,args中的字符串被移走,变成空字符串了    int a4 = ( ... + GetVal( args ) );    cout << a1 << " " << a2 <<  " " << a3 << " " << a4 << endl;    // <11> a1 、a2 、a3 的值都一样(5+6+7+8=26),但 a4 的值会小 8,因为字符串被移走了    return ( a1 + a2 + a3 ) / 3;   }int main( int argc, char * argv[] ){    std::string str = "8";    int a1 {};    a1 = Sum( 5, 6.1, "7", str );    cout << a1 << endl;      return 0;}

编译和运行结果为:

[smlc@test code]$ g++ -std=c++17 a14.cppa14.cpp: In function 'int Sum(VT ...)':a14.cpp:37:13: warning: pack init-capture only available with '-std=c++2a' or '-std=gnu++2a'   37 |  int a3 = [ ... newargs = std::move( args ) ] {      |             ^~~[smlc@test code]$ g++ -std=c++20 a14.cpp[smlc@test code]$ ./a.out26 26 26 1826

【往期回顾】

C++20 新特性(13):无状态lambda可以构建和赋值

C++20 新特性(12):使用模板语法的泛型lambda

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值