漂亮的折叠表达式技巧

2020-05-10 07:08:08

假设您需要一个变量函数,并希望将所有参数相加在一起。在C++17之前,您需要两个伪递归函数:

模板TypeName H,TypeName(<;TypeName).。自动添加(H头,T.。Tail){Return Head+Add(Tail.);}模板<;typeName H>;自动添加(H Head){Return Head;}。

模板TypeName H,TypeName(<;TypeName).。自动添加(H头,T.。Tail){return(Head+.+Tail);//展开为:Head+Tail[0]+Tail[1]+.}。

如果我们愿意滥用运算符求值规则和折叠表达式,我们可以做得更多。这篇博客文章收集了一些有用的技巧。

只要有可能,我们就应该使用折叠表达式来处理参数包,而不是使用递归:

这是更快的代码(没有优化),因为您只有一个表达式,而不是多个函数调用。

缺点是,它通常是不可读的,需要额外的注释来解释正在发生的事情。

如果包的所有参数都具有相同的类型,我们可以通过编写auto list={pack.}将它们放在初始化列表中,然后使用常规循环。但是,使用折叠表达式可以免费展开循环,这有时是可取的。

在下面的所有代码片段中,ts是我们的可变包,f()是可以获取包的每个元素的函数,pred()是每个包的某个谓词。f()和pred()不需要是文字函数,它们可以是一次使用一个元素的任意表达式。

我们在每个元素上调用函数并叠加逗号运算符,保证从左到右计算结果表达式,即按顺序计算。

int Dummy;(Dummy=.=(f(Ts),0));//展开为:Dummy=((f(ts[0]),0)=(f(ts[1]),0))=.。

为了反向调用函数,我们需要一个从右到左计算参数的运算符。在这样的运算符==:a=b=c上,首先计算c,然后是b,然后是a。因此,我们使用逗号运算符将函数调用结果转换为某个int值,然后将其作为赋值合并到一个伪变量中。我们最终得到了一个很大的赋值表达式,其中每个操作数首先调用函数,然后结果为0,并以相反的顺序进行计算。

如果您写Dummy=0=0,这本质上就是我们所拥有的,它将不会编译:=是右关联的,因此该表达式等价于Dummy=(0=0),并且您不能赋值0。但是,我们在这里做的是一个左折叠,它放入等价于(Dummy=0)=0的括号,这将赋值给Dummy Two。我们得到的是一个从右到左求值的左关联表达式!

((Pred(Ts)?FALSE:(F(Ts),true))&;&;.);//扩展为:(pred(ts[0])?FALSE:(F(ts[0]),true))//&;&;(pred(ts[1])?FALSE:(F(ts[1]),true))//&;&;.。

我们在每个元素上调用谓词。如果返回TRUE,则结果为FALSE;否则,调用函数,结果为TRUE,然后使用&;&;折叠它,它从左到右求值,并在第一个假结果(即谓词匹配时)停止。

我们将谓词调用折叠到||上,如果任何谓词返回TRUE,则返回TRUE。|从左到右和短路求值,因此在一个元素返回true后不会调用谓词。

自动计数=(std::size_t(0)+.+(pred(Ts)?1:0));//扩展为:std::size_t(0)+(pred(ts[0])?1:0)//+(pred(ts[1])?1:0)//+.。

我们将每个元素转换为0或1,取决于它是否与谓词匹配,然后将其相加,空包的初始值为0。

std::common_type_t<;decltype(Ts).。>;result;bool find=((pred(Ts)?(result=ts,true):false)||.);//展开为:(prd(ts[0])?(result=ts[0],true):false)//||(pred(ts[1])?(result=ts[1],true):false)//||.。

只有当所有t都具有默认可构造的公共类型时,这才起作用。

我们检查每个元素,如果找到一个,则将其存储在一个变量中,结果为True。如果它与谓词不匹配,则结果为False。然后,我们将||折叠起来,从左到右求值,并在第一个True结果(即,当我们找到元素时)停止。

std::common_type_t<;decltype(Ts).。>;result;std::size_t i=0;((i++==n?(result=ts,true):false)||.);//展开为:(i++==n?(result=ts[0],true):false)//||(i++==n?(result=ts[1],true):false)//||..。

只有当所有t都具有默认可构造的公共类型时,这才起作用。

我们记住当前索引,并为每个元素递增。一旦到达目标索引,我们就记住该元素,结果为真。否则,我们什么也不做,结果为假。然后,我们将||折叠起来,从左到右求值,并在第一个真结果(即,当我们在所需索引处找到元素时)停止。

std::common_type_t<;decltype(Ts).。>;result;((result=ts,true)||.);//展开为:(result=ts[0],true)//||(result=ts[1],true)//||.。

只有当所有t都具有默认可构造的公共类型时,这才起作用。

我们将每个元素存储在RESULT中,而RESULT存储在TRUE中,然后折叠||,从左到右进行计算,并在第一个TRUE结果(即紧接在第一次赋值之后)停止。

我们只需使用逗号操作符折叠所有元素,其结果就是最后一个表达式,即最后一个元素。

如果包是空的,您将得到一个编译器错误,因为结果将是无效的。

自动最小=ts[ts。size()-1];for(auto elem:ts)if(elem<;min)min=elem;

AUTO MIN=(ts,.);((ts<;min?min=ts:0),.);//扩展为:(ts[0]<;min?min=ts[0]:0),//(ts[1]<;min?MIN=ts[1]:0),//.。

我们将最小值设置为最终值,然后将每个值与最小值进行比较。如果小于,则更新最小值。0就在那里,所以我们在?:的另一个分支中有一些表达式。

通常,算法会从第一个值开始作为起始值。但是,获得包的最后一个值更简单,所以我们改为这样做。

如果你喜欢这篇博客文章,可以考虑支持我--每月一美元真的能帮到我。