一句话概括:
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 <typename ... Args>
constexpr auto unary_right_sub(Args... args);
fold expression 的详细定义参见 [fold expression]
parameter pack 的详细定义参见 [parameter pack]
在 C++17 中,总共定义有四种类型的 fold expression, 我们结合例子来看一下:
语法:
( 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 是怎么展开的:
/*** 1. unary right fold ***/
template <typename ... Args>
constexpr auto unary_right_sub(Args... args){
return (args - ...); // unary right fold expression.
}
/*** test unary right fold ***/
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 <typename T1, typename T2>
constexpr auto recursive_right_sub(T1 arg1, T2 arg2){
return (arg1 - arg2);
}
template <typename T1, typename ... Args>
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。
语法:
( … op E )
扩展为
( ( ( E1 op E2 ) op … ) op EN )
代码举例:
/*** 2. unary left fold ***/
template <typename ... Args>
constexpr int unary_left_sub(Args... args){
return (... - args); // unary left fold expression.
}
/*** test unary left fold ***/
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)
。
语法:
( 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 <typename ElementType, typename TupleType>
constexpr auto operator>>(ElementType&& e, TupleType&& t){
return std::tuple_cat(std::make_tuple(e), t);
}
接着,我们再定义 binary_right_cat_tuple 函数:
/*** 3. binary right fold ***/
template <typename TupleType, typename ... Args>
constexpr auto binary_right_cat_tuple(const TupleType& init_tuple, Args ... args){
return ( args >> ... >> init_tuple );
}
/*** test binary right fold ***/
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<decltype(concat_tuple), decltype(make_concat_tuple)>);
上面代码中,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 打印出来。
语法:
( 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 <typename T, std::enable_if_t<!std::is_same_v<T, std::string>, bool> = true >
auto to_string_helper(T t){
return std::to_string(t);
}
template <typename T, std::enable_if_t<std::is_same_v<T, std::string>, bool> = true >
auto to_string_helper(T t){
return t;
}
接着,我们定义用 binary left fold 实现的打印函数 binary_left_print_tuple
:
/*** 4. binary left fold ***/
// print tuples.
template <typename TupleType, size_t ...Idx>
constexpr void binary_left_print_tuple(TupleType& t, std::index_sequence<Idx...>){
( std::cout << ... << (to_string_helper(std::get<Idx>(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);
/*** test binary left fold ***/
constexpr auto concat_tuple_size = std::tuple_size_v<decltype(concat_tuple)>;
std::cout << "-- the elements of concat_tuple: " << std::endl;
binary_left_print_tuple(concat_tuple, std::make_integer_sequence<size_t, concat_tuple_size>());
上面代码中,binary_left_print_tuple
函数中的 binary left fold 表达式即为:
( std::cout << ... << (to_string_helper(std::get
我们对照上文中的语法定义:
E 是 (to_string_helper(std::get
,这里 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 << "Jack\n"s ) << "3.141593\n" ) << "hello world\n"s ) << "1988\n" ) << "stay calm\n" )
代码运行得到的打印结果如下:
-- 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]