栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++17 关键新特性介绍及代码讲解 (6) — fold expression

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++17 关键新特性介绍及代码讲解 (6) — fold expression

C++17 关键新特性介绍及代码讲解 (6) — fold expression

一句话概括:

fold expression 是将 parameter pack 进行递归展开、并通过二元操作符 (binary operator) 进行运算的一种表达式。

parameter pack 包括: template parameter pack、 function parameter pack, 以下面代码中的函数声明为例,
typename ... Args为 template parameter pack, Args... args为 function parameter pack:

template 
constexpr auto unary_right_sub(Args... args);

fold expression 的详细定义参见 [fold expression]

parameter pack 的详细定义参见 [parameter pack]

在 C++17 中,总共定义有四种类型的 fold expression, 我们结合例子来看一下:


1. unary right fold

语法:

( E op … )

扩展为

( E1 op ( … op ( EN-1 op EN ) ) )

E 的定义 :一个包含未展开的 parameter pack 的表达式;如上文定义的,parameter pack 可以是 template parameter pack 或者
function parameter pack;

op: 即二元操作符,且可以被重载,包括:

+  -  *  /  %  ^  &  |  ~  =  <  >  <<  >> +=  -=  *=  /=  %=  ^=  &=  |=  <<=  >>= ==  !=  <=  >=  &&  ||  ,  .*  ->*         

N:parameter pack 中包含的元素个数;

在后文将要介绍到的另外三种类型 fold expression 的定义中,E 和 op 的定义与这里相同,不再赘述;

我们通过下面这段代码,来看看 unary right fold 是怎么展开的:


template 
constexpr auto unary_right_sub(Args... args){
    return (args - ...);  // unary right fold expression.
}


constexpr auto u_r_sub = unary_right_sub(1, 2, 3, 4);
static_assert(u_r_sub == (1 - (2 - (3 - 4))));

上面代码中,unary_right_sub函数中的 unary right fold 表达式是:(args - ...)。

即:

E 是 args,即仅包含 function parameter pack 的一个表达式;

op 是 -操作符;

unary_right_sub(1, 2, 3, 4)最终展开为 (1 - (2 - (3 - 4)))) 。

对比一下,在cpp17之前,如果要实现上述的 unary_right_sub 函数功能,我们需要用递归函数的方法展开:

// before C++17, we must use the recursive implementation for variadic template.
// additional stop case function must be defined here.
template 
constexpr auto recursive_right_sub(T1 arg1, T2 arg2){
    return (arg1 - arg2);
}
template 
constexpr auto recursive_right_sub(T1 arg1, Args... args) {
    return (arg1 - recursive_right_sub(args...));
}

// test of recursive implementation. 
constexpr auto u_r_sub = unary_right_sub(1, 2, 3, 4);
constexpr auto r_r_sub = recursive_right_sub(1, 2, 3, 4);
static_assert(u_r_sub == r_r_sub);

可以看到,fold expression 这种写法的好处是,大大简化了 variadic template 的实现,大多数情况下,不再需要用递归函数的方式去实现
variadic template。


2. unary left fold

语法:

( … op E )

扩展为

( ( ( E1 op E2 ) op … ) op EN )

代码举例:


template 
constexpr int unary_left_sub(Args... args){
    return (... - args); // unary left fold expression.
}


constexpr auto u_l = unary_left_sub(1, 2, 3, 4);
static_assert(u_l == ((( 1 - 2 ) - 3 ) - 4));

即,unary_left_sub(1, 2, 3, 4)通过 (... - args) 扩展为了 ((( 1 - 2 ) - 3 ) - 4) 。


3. binary right fold

语法:

( E op … op I)

扩展为

( E1 op ( … op ( EN-1 op ( EN op Init ) ) ) )

Init 的定义 : 一个没有包含未展开的 parameter pack 的表达式;

代码举例:假设我们有一个已经包含初始元素的 tuple 数据结构,接着我们需要实现一个叫做 binary_right_cat_tuple的函数,
该函数接受任意类型、任意个数的参数,并且将这些参数,按顺序添加到初始的 tuple 中,且位置位于 tuple 中所有初始元素的序列前。

为了实现方便,我们先重载一下 >>二元运算符:重载后的功能是将一个任意类型的参数,同一个已知的 tuple 数据结构,
"拼接"为一个全新的 tuple:

// overload '>>' operator.
template 
constexpr auto operator>>(ElementType&& e, TupleType&& t){
    return std::tuple_cat(std::make_tuple(e), t);
}

接着,我们再定义 binary_right_cat_tuple 函数:


template 
constexpr auto binary_right_cat_tuple(const TupleType& init_tuple, Args ... args){
    return ( args >> ... >> init_tuple );
}

using namespace  std::literals::string_literals;
const auto init_tuple = std::make_tuple("stay calm"s);
const auto concat_tuple = binary_right_cat_tuple(init_tuple, "Jack"s, M_PI,  "hello world"s, 1988);
// just for compare.
const auto make_concat_tuple = std::make_tuple("Jack"s, M_PI,  "hello world"s, 1988, "stay calm"s);
static_assert(std::is_same_v);

上面代码中,binary_right_cat_tuple函数中的 binary right fold 表达式即为:

( args >> ... >> init_tuple )

我们对照上文中的语法定义:

E 是 args,即仅包含 function parameter pack 的一个表达式;

op 是重载过的 >>操作符;

Init 是 init_tuple,即初始的 tuple 数据结构;

即,

binary_right_cat_tuple(init_tuple, "Jack"s, M_PI, "hello world"s, 1988)

最终展开为

( "Jack"s >> ( M_PI >> ( "hello world"s >> ( 1988 >> init_tuple ) ) ) )

最后,我们得到的 concat_tuple 即包含 "Jack"s, M_PI, "hello world"s, 1988, "stay calm"s 所有元素。

下面我们讲 binary left fold 的代码例子里,会把concat_tuple 打印出来。


4. binary left fold

语法:

( I op … op E )

扩展为

( ( ( ( I op E1) op E2 ) op … ) op EN )

代码举例 :我们想要实现一个打印函数,可以接受任意类型的 tuple 参数,并将 tuple 的每个元素逐行打印出来。

为了实现方便,我们先定义 to_string_helper函数:该函数可以将非 string 类型的参数转换为 string,而对于 string 类型的
参数则不做转换,这个与 fold expression 无关,大家可以略过:

// helper functions.using SFINAE.
template , bool> = true >
auto to_string_helper(T t){
    return std::to_string(t);
}
template , bool> = true >
auto to_string_helper(T t){
    return t;
}

接着,我们定义用 binary left fold 实现的打印函数 binary_left_print_tuple:


// print tuples.
template 
constexpr void binary_left_print_tuple(TupleType& t, std::index_sequence){
    ( std::cout << ... << (to_string_helper(std::get(t)) + "n" ) );
}

using namespace  std::literals::string_literals;
const auto init_tuple = std::make_tuple("stay calm"s);
const auto concat_tuple = binary_right_cat_tuple(init_tuple, "Jack"s, M_PI,  "hello world"s, 1988);


constexpr auto concat_tuple_size = std::tuple_size_v;
std::cout << "-- the elements of concat_tuple: " << std::endl;
binary_left_print_tuple(concat_tuple, std::make_integer_sequence());

上面代码中,binary_left_print_tuple函数中的 binary left fold 表达式即为:

( std::cout << ... << (to_string_helper(std::get(t)) + "n" ) )

我们对照上文中的语法定义:

E 是 (to_string_helper(std::get(t)) + "n" ),这里 Idx 是 non-type template parameter pack;表达式 E
的功能是将 tuple 数据结构 t中的元素按照 Idx 的值索引取出,再调用 to_string_helper函数转换为 string 类型,
最后添加换行符 "n"; 这里我们看到, E 可以包含各种函数调用;

op 是标准定义的 << 操作符;

Init 是 std::cout ,即系统标准输出流;

即:

binary_left_print_tuple(concat_tuple, std::make_integer_sequence())

最终展开为

( ( ( ( ( std::cout << "Jackn"s ) << "3.141593n" ) << "hello worldn"s ) << "1988n" ) << "stay calmn" )

代码运行得到的打印结果如下:

-- the elements of concat_tuple: 
Jack
3.141593
hello world
1988
stay calm

完整 demo代码参见 gitee.com 上的公开代码仓库 : [fold expression]


扩展阅读:

fold 的概念来自于 functional programming,参见这篇文章 [Folding things with Functional Programming]

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/968280.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号