前言 C++11 提供了可变模板参数包,使函数可以接受任意数量的参数。但在 C++11中展开参数包稍显麻烦,而 C++17 的折叠表达式使得展开参数包变得容易,其基本语法是使用 (…) 的语法形式进行展开。
支持的操作符 折叠表达式支持 32 个操作符:1
2
3
+, -, *, /, %, ^, &, |, =, <,
>, <<, >>, +=, -=, *=, /=, %=, ^=, &=, |=, <<=,
>>=,==, !=, <=, >=, &&, ||, ,, .*, ->*.
折叠分类 C++17 的折叠表达式根据参数包的位置分为左折叠和右折叠,根据操作的对象数量分为一元折叠和二元折叠。 例 1:左折叠1
2
3
4
5
6
7
8
9
10
11
template <typename ... Ts>
auto sum (Ts ... ts)
{
return (... + ts);
}
int the_sum {sum(1 , 2 , 3 , 4 , 5 )};
std ::string a {"Hello " };
std ::string b {"World" };
std ::string the_string {sum(a, b)};
例 2:右折叠1
2
3
4
5
template <typename ... Ts>
auto sum (Ts ... ts)
{
return (ts + ...);
}
例1中,参数包 … 位于操作符的左侧,故尔称为左折叠。如果 …位于操作符右侧,则称为右折叠,如例 2 所示。就例 1 与例 2 而言,左折叠与右折叠的效果是相同的。 对于1
int the_sum {sum(1 , 2 , 3 , 4 , 5 )};
左折叠的展开方式为1
1 + (2 + (3 + (4 + 5 ))),
右折叠的展开方式为
在例 1 与 例 2 中,如果参数包包含的参数数量为 0,即为空包,会产生编译错误,如
大致的错误输出如下1
2
3
In instantiation of 'auto sum(Ts ...) [with Ts = {}]':
error: fold of empty expansion over operator+
return (... + ts);
若要解决空参数包的编译错误,针对例 1,可以加上一个数值 0,可以解决编译错误又可以使得语义不变,这个 0 相当于缺省值。通过加上一个数值,折叠就变成了二元折叠,如例 3 所示。 例 3:二元折叠1
2
3
4
5
6
7
8
9
template <typename ... Ts>
auto sum (Ts ... ts)
{
return (ts + ... + 0 );
}
此时对于1
int the_sum {sum(1 , 2 , 3 , 4 , 5 )};
折叠的展开方式为1
1 + (2 + (3 + (4 + (5 + 0 ))))
空参数包 空参数包就是参数包中不含任何参数。对于大多数操作符,空参数包将会引发编译错误。对于 && 或 ||,空参数包是合法的,其中 && 的展开结果为 true,||的展开结果为 false。在逗号,操作符中,空参数包也合法,展开为 void()。
其它例子 例 4:计算指定区间内包含指定数值的个数1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename R, typename ... Ts>
auto count (const R& range, Ts ... ts)
{
return (std ::count(std ::begin(range), std ::end(range), ts) + ...);
}
...
std ::vector <int > v {1 , 2 , 3 , 4 , 5 };
count(v, 2 , 5 );
count(v, 100 , 200 );
count("abcdefg" , 'x' , 'y' , 'z' );
count("abcdefg" , 'a' , 'd' , 'f' );
例 5:检查插入多个元素是否成功1
2
3
4
5
6
7
8
9
10
11
12
template <typename T, typename ... Ts>
bool insert_all (T &set , Ts ... ts)
{
return (set .insert(ts).second && ...);
}
...
std ::set <int > my_set {1 , 2 , 3 };
insert_all(my_set, 4 , 5 , 6 );
insert_all(my_set, 7 , 2 , 8 );
最后 1. 对于一元右折叠 (E op …) 具体展开为 E1 op (… op (EN-1 op EN))。 2. 对于一元左折叠 (… op E) 具体展开为 (( E1 op E2) op …) op En。 3. 对于二元右折叠 (E op … op I) 具体展开为 E1 op (… op (EN-1 op (EN op I)))。 4. 对于二元左折叠 (I op … op E) 具体展开为 (((I op E1) op E2) op …) op E2。 左折叠与右折叠的语义并非总是相同的。比如对于加法和乘法,左折叠与右折叠的语义是相同的,但是对于减法与除法,其语义是不同的。 例 6:左右折叠不同语义1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename ... Args>
auto left_sub (Args&&... args)
{
return (... - args);
}
template <typename ... Args>
auto right_sub (Args&&... args)
{
return (args - ...);
}
...
auto a = left_sub(2 , 3 , 4 );
auto b = right_sub(2 , 3 , 4 );
co_await promise.yield_value(expression);