C++17 详解 12

本文为 《C++17 in detail》 一书的中文渣中渣译文,不足之处还望指正。

4.2 折叠表达式

C++11 引入了可变参模版,这是一个非常强力的特性,特别是你想实现一个有可变数量的模版参数的函数时。比如,(C++11)之前你不得不实现多个不同版本的模版函数(一个带一个参数、另一个带两个参数、另一个带三个参数……)。

当你想实现类似 sum 这样的递归函数时,可变参模版仍然需要一些额外的代码。你必须指明递归规则。

比如:

1
2
3
4
5
6
7
8
auto SumCpp11(){
return 0;
}

template<typename T1, typename... T>
auto SumCpp11(T1 s, T... ts){
return s + SumCpp11(ts...);
}

通过 C++17 我们可以把代码写得更简单:

1
2
3
4
5
6
7
8
9
10
template<typename ...Args> auto sum(Args ...args)
{
return (args + ... + 0);
}

// 或者甚至:
template<typename ...Args> auto sum2(Args ...args)
{
return (args + ...);
}

有如下几种使用二元操作符的折叠表达式的变种:

表达式 名称 解释
(... op e) 一元左折叠 ((e1 op e2) op ...) op eN
(init op ... op e) 二元左折叠 (((init op e1) op e2) op ...) op eN
(e op ...) 一元右折叠 e1 op (... op (eN-1 op eN))
(e op ... op init) 二元右折叠 e1 op (... op (eN-1 op (eN op init)))

译注:注意,折叠表达式外层必须有一个括号

op 可以是下边 32 种二元操作符中的任意一个:+ - * / % ^ & | = < > << >> += -= *= /= %= ^= &= |= <<= >>= == != <= >= && || , .* ->* 。在一个二元折叠里,两个 op 必须一样。

比如,当你写下:

1
2
3
4
5
6
template<typename ...Args> auto sum2(Args ...args)
{
return (args + ...); // unary right fold over '+'
}

auto value = sum2(1, 2, 3, 4);

模版函数被展开为:

1
auto value = 1 + (2 + (3 + 4));

对空参数包,默认得到下列值:

操作符 默认值
&& true
|| false
, void()
其它 错误

这也是为什么你不能不带任何参数调用 sum2,因为 + 操作符上的一元折叠对空参数列表没有任何默认值。

更多示例

这里有一个使用折叠的非常好的 printf 实现 P0036R0

1
2
3
4
5
6
7
template<typename ...Args>
void FoldPrint(Args&&... args)
{
(std::cout << ... << std::forward<Args>(args)) << '\n';
}

FoldPrint("hello", 10, 20, 30);

但是上边的 FoldPrint 一个接一个地打印参数,没有任何分隔符。所以你会看到 “hello102030” 这样的输出。

如果想要分隔符和其它更多格式化选项,你必须调整打印技巧,在逗号操作符上使用折叠:

1
2
3
4
5
6
7
8
template<typename ...Args>
void FoldSeparateLine(Args&&... args)
{
auto separateLine = [](const auto& v) {
std::cout << v << '\n';
};
(... , separateLine (std::forward<Args>(args))); // over comma operator
}

这种通过逗号操作符使用折叠的技巧很方便。另一个例子是一个 push_back 的特殊版本:

1
2
3
4
5
6
7
8
template<typename T, typename... Args>
void push_back_vec(std::vector<T>& v, Args&&... args)
{
(v.push_back(std::forward<Args>(args)), ...);
}

std::vector<float> vf;
push_back_vec(vf, 10.5f, 0.7f, 1.1f, 0.89f);

一般来讲,折叠表达式能让你写出更清晰、更短小、也可能读起来更令人愉悦的代码。

扩展:本修改提案:N4295P0036R0

评论