折叠表达式
C++17之前处理参数包
C++17折叠表达式的出现让我们不必再用递归实例化模板的方式来处理参数包。
在 C++17之前,实现必须分成两个部分:
- 终止条件
- 递归条件
这样的实现不仅写起来麻烦,对 C++编译器来说也很难处理。C++17折叠表达式能显著的减少程序员和编译器的工作量
// 终止条件
// - 终止条件要写在递归条件之前
template<typename First>
First foldSum1(First&& value)
{
return value;
}
// 递归条件
template<typename First, typename... Rest>
First foldSum1(First&& first, Rest&&... rest)
{
return first + foldSum1(std::forward<Rest>(rest)...);
}
auto i1 = foldSum1(58, 25, 128, -10); //201
auto s1 = foldSum1(std::string("a "), std::string("b "), std::string("c"));//"a b c"
C++17 折叠表达式 ... op
用途
自从 C++17起,新特性 折叠表达式 可以计算对参数包中的所有参数应用一个二元运算符的结果。
**目的:**不必再用递归实例化模板的方式来处理参数包(简化对C++11中引入的参数包的处理)
可以使代码更简洁、更易于阅读,但也可能会使代码更难以理解。需要谨慎使用,并考虑是否有其他更直观的方法可以达到相同的效果。
template<typename... T>
auto foldSum2(T... args)
{
// 一元 无初值,不允许传递空参数包
// - 左折叠
return (... + args); // ((arg1 + arg2) + arg3) ...
// - 右折叠
return (args + ...); // ((arg3 + arg2) + arg1) ...
// 二元 有初值,允许传递空参数包
// - 左折叠
return (0 + ... + args); // 0 + ((arg1 + arg2) + arg3) ...
// - 右折叠
return (args + ... + 0); // ((arg3 + arg2) + arg1) ... + 0
}
auto i2 = foldSum2(58, 25, 128, -10); //201
auto s2 = foldSum2();//"a b c"
展开规则
折叠表达式共有四种语法形式:一元 二元,左折叠 右折叠 的两两组合。
-
一元 二元:
-
不存在/存在
初值 -
对空参数包处理不同(二元一定允许,一元未必允许。见下文)
-
-
左折叠 右折叠:
- 初始值在
左边/右边
- 表达式求值顺序,从
左边/右边
开始——展开之后从左边/右边
开始折叠
- 初始值在
示例中符号的含义如下:
op
:运算符args
:参数包,含有未展开的形参包value
:初始值,不含未展开的形参包
注意事项
- 优先选,左折叠表达式
(... + args);
、(value op ... op args);
,因为对大多数人来说从左向右求值更符合直觉。 ()
开闭括号也是折叠表达式的一部分,不能被省略args
和value
不含优先级低于 转型表达式 的运算符的表达式。
# 1.一元左折叠(unary left fold)
# - 展开前
( ... op args )
# - 展开后
((arg_1 op arg_2) op ...) op arg_N
((1 + 2) + ...) + 9
# 2.二元左折叠(binary left fold)
# - 展开前
(value op ... op args)
# - 展开后
(((arg_value op arg_1) op arg_2) op ...) op arg_N
(((0 + 1) + 2) + ...) + 9
# 3.一元右折叠(unary right fold)
# - 展开前
(args op ...)
# - 展开后
arg_1 op (... op (arg_N-1 op arg_N))
1 + ( ... + (8 + 9))
# 4.二元右折叠(binary right fold)
# - 展开前
(args op ... op value)
# - 展开后
arg_1 op (... op (arg_N−1 op (arg_N op arg_value)))
1 + ( ... + (8 + (9 + 0))
sizeof…
template <class ...Ts>
constexpr auto get_common_type(Ts ...ts) {
if constexpr (sizeof...(Ts) == 0) {}
if constexpr (sizeof...(ts) == 0) {
处理空参数包
一元折叠
将一元折叠用于长度为零的包展开时:
运算符 | 空包的值 |
---|---|
逻辑与 && | true |
逻辑或 ` | |
逗号运算符, | void() |
所有其他的运算符 | 格式错误,编译失败 |
template<typename... Args>
bool all(Args... args) { return (... && args); }
template<typename... Args>
bool any(Args... args) {return (... || args);}
bool b = all(true, true, true, false);
// 在 all() 中,一元左折叠展开成
// return ((true && true) && true) && false;
// b 是 false
二元折叠
一定允许空参数包。
合法的操作符 opt
二元运算符
二元运算符:合法的有32 个
+ - * / %
^ & | = < > << >>
+= -= *= /= %= ^= &= |= <<= >>= == != <= >=
&& || , .* ->*
注意:默认情况下 lambda以值返回对象,这意味着会创建参数的不必要的拷贝。
解决方法:显式指明返回类型为 const auto&或者 decltype(auto):
template<typename First, typename... Args>
void print (const First& firstarg, const Args&... args) {
std::cout << firstarg;
auto spaceBefore = [](const auto& arg) ‐> const auto& {
std::cout << ' ';
return arg;
};
(std::cout << ... << spaceBefore(args)) << '\n';
}
template<typename First, typename... Args>
void print (const First& firstarg, const Args&... args) {
std::cout << firstarg;
(std::cout << ... << [] (const auto& arg) ‐> decltype(auto) {
std::cout << ' ';
return arg;
}(args)) << '\n';
}
二元操作符
所有的二元操作符
.
、->
、[]
,
逗号运算符(左叠表达式可以使用)
折叠函数调用
折叠表达式可以用于逗号运算符 —— 因此可以在一条语句里进行多次函数调用。
// 对所有参数调用函数 foo()
template<typename... Types>
void callFoo(const Types&... args)
{
(... , foo(args)); // 调用foo(arg1),foo(arg2),foo(arg3),...
// 如果需要支持移动语义
(... , foo(std::forward<Types>(args)));
// 如果 foo()函数返回的类型重载了逗号运算符,那么代码行为可能会改变
// 安全的做法: 把返回值转换为 void
(... , (void)foo(std::forward<Types>(args))); // 调 用foo(arg1),foo(arg2),...
}
例如:组合哈希函数
template<typename T>
void hashCombine (std::size_t& seed, const T& val)
{
seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
template<typename... Types>
std::size_t combinedHashValue (const Types&... args)
{
std::size_t seed = 0; // 初始化seed
(... , hashCombine(seed, args)); // 链式调用hashCombine()
return seed;
}
combinedHashValue ("Hi", "World", 42);
// hashCombine(seed, "Hi"), hashCombine(seed, "World"), hashCombine(seed, 42);
玩的骚一点,可以调用可变数量基类的成员函数
#include <iostream>
// 可变数量基类的模板
template<typename... Bases>
class MultiBase : private Bases...
{
public:
void print() {
// 调用所有基类Bases...的print()函 数
(... , Bases::print());
}
};
struct A { void print() { std::cout << "A::print()\n"; }}
struct B { void print() { std::cout << "B::print()\n"; }}
struct C { void print() { std::cout << "C::print()\n"; }}
int main()
{
MultiBase<A, B, C> mb;
mb.print();
// (A::print(), B::print()), C::print();
}
处理类型
配合 SFINAE 或者 concept,可以使用折叠表达式处理模板参数包。
像通常一样,运算符 &&会短路求值(出现第一个 false时就会停止运算)
例如:检查模板参数包Ts ...ts
与某一类型是否存在关系。
template <class T0, class ...Ts>
// 可以显式转换(过于宽松)
// requires ((true && ... && std::is_constructible_v<Ts, T0>))
// 可以隐式转换
requires ((true && ... && std::is_convertible_v<Ts, T0>))
// 完全相同(过于严格)
// requires ((true && ... && std::is_same_v<Ts, T0>))
std::array<T0, sizeof...(Ts) + 1> myvec(T0 t0, Ts ...ts) {
return {t0, ts...};
}
例如:类型是否有任意一个可以转换
// - Ts中 是否有任意一个 可以与T转换
template <class T, class ...Ts>
struct is_convertible_any {
static constexpr bool value = (false || ... || std::is_convertible_v<T, Ts>);
};
例如:类型是否有任意一个相同
// - Ts中 是否有任意一个 与T类型相同
template <class T, class ...Ts>
struct is_same_any {
static constexpr bool value = (false || ... || std::is_same_v<T, Ts>);
};
标签:std,...,17,折叠,args,C++,&&,表达式,op
From: https://blog.csdn.net/weixin_41733034/article/details/143352160