跟我学c++中级篇——c++17中的折叠表达式

一、折叠表达式

fold expression,折叠表达式,为什么要出这个东西?其实目的指向性非常明白,仍然是简化编程。但是有一个问题,不断的简化编程方向性一定是好的。但带来一个副作用,可能一些没接触过的c++开发人员会一脸蒙圈。折叠表达式长什么样子,在开发者眼里,它就是三个点(…),和变参模板里的一样,三个点。可能严格意义上讲不是这样,但对于刚刚接触的人来说就只会专注到这三个点上。
那么什么是折叠表达式呢?“以二元运算符对形参包进行规约”。太抽象了,不明白。
先搞一下什么是fold?“In functional programming, fold (also termed reduce, accumulate, aggregate, compress, or inject) refers to a family of higher-order functions that analyze a recursive data structure and through use of a given combining operation, recombine the results of recursively processing its constituent parts, building up a return value. Typically, a fold is presented with a combining function, a top node of a data structure, and possibly some default values to be used under certain conditions. The fold then proceeds to combine elements of the data structure’s hierarchy, using the function in a systematic way”.
这些话的主要意思很明白,第一,它是在函数式编程语言中应用起来的;第二,它通过递归的方式搞定解析;第三,要有给定的组合操作。举一个简单的例子,对集合{1100}求和,其实就可以搞成1+{2100},1+{2+{3~100}…},看到没,递归+和操作。一般在编程中这个操作叫Sum。把这个抽象一下,就是折叠,fold(fn,start,list),其中fn是Sum,list是操作的内容,另外的是启动初始值。折叠有两种形势,其实就是两种递归方式,一个是前向递归一个是后向递归,而后者又可以通过循环优化。
理解了折叠,再来理解折叠表达式,就明白了,就是对一个表达式搞折叠呗。有折叠就有展开。展开就容易理解了吧,把这个折叠回复成可以正常识别的式子。
折叠表达式这样看来,应该是向函数式语言学引入的一个技术,它在简化递归进行实例化函数参数模板的操作,使更加清晰也更加简便。

二、用法

折叠表达式的用法如下:

语法
折叠表达式	 
( pack op ... )	(1)	
( ... op pack )	(2)	
( pack op ... op init )	(3)	
( init op ... op pack )	(4)	
 
1) 一元右折叠
2) 一元左折叠
3) 二元右折叠
4) 二元左折叠
运算符	-	下列 32 个二元运算符之一:+ - * / % ^ & | = < > << >> += -= *= /= %= ^= &= |= <<= >>= == != <= >= && || , .* ->*。在二元折叠中,两个 运算符 必须相同。
形参包	-	含有未展开的形参包且在顶层不含优先级低于转型(正式而言,是 转型表达式)的运算符的表达式
初值	-	不含未展开的形参包且在顶层不含优先级低于转型(正式而言,是 转型表达式)的运算符的表达式
注意开闭括号也是折叠表达式的一部分。

解释
折叠表达式的实例化按以下方式展开成表达式 e:

1) 一元右折叠 (E op ...) becomes (E1 op (... op (EN-1 op EN)))
2) 一元左折叠 (... op E) becomes (((E1 op E2) op ...) op EN)
3) 二元右折叠 (E op ... op I) becomes (E1 op (... op (EN−1 op (EN op I))))
4) 二元左折叠 (I op ... op E) becomes ((((I op E1) op E2) op ...) op EN)
(其中 N 是包展开中的元素数量)

折叠表达式的用法,最典型就是就是应用在变参模板上。可以大大的节省代码数量,直观上会更清爽,至于易不易于理解,就不好说了。

三、例程

用一个传统的变参模板的例子就很好的诠释了这个折叠表达式:

//传统写法
int TestVT()
{
    return 1;
}

template
int TestVT(T var0, Args... varn)
{ 
    std::cout << "args len is:" << sizeof...(varn) << std::endl;
    return var0 + TestVT(varn...);
}


int main()
{
    std::cout << "value is:"<< TestVT(1, 2, 3)<< std::endl;
    system("pause");

    return 0;
}

//变参写法
template 
int TestVT(T t) {
    std::cout << "cur value:" << t << std::endl;
    return t;
}

//c++17折叠表达式展开
template 
void TestVT(Args... args)
{
    //逗号表达式+初始化列表
    //int v[] = { (Add1(args),0)... };
 
    int d = (Add1(args)+ ...);
    std::cout << "cur sum:" << d << std::endl;
}


int  main()
{
    TestVT(1, 2,7);
    return 0;
}

几乎是大部分的运算符都支持折叠表达式,当然不支持的也有,有些特殊的,大家遇到注意即可。既然是可以处理变参,那么变参基类也可以折叠,变参数据类型也可以折叠。

template
class Sub : private Bases...
{
public:
    void Display()
    {
        (..., Bases::Display());
    }
};
class B1 {
public:
    void Display() { std::cout << "call B1 function" << std::endl; }
};
class B2 {
public:
    void Display() { std::cout << "call B2 function" << std::endl; }
};

int main()
{
    Sub s;
    s.Display(); 
    return 0;
}

前面提到的变参是0个时也能折叠,但是就不能使用+和-号这些二元操作符了。只能使用与或非以及逗号等OP了。

template 
int IsTrue(const Args&... args) {
    return (... && args);
}

template 
int eitherTrue(const Args&... args) {
    return (... || args);
}

void ZeroEp() {
    std::cout << IsTrue(1, 1, 0) << IsTrue(1, 1) << IsTrue() << std::endl;
    std::cout << eitherTrue(1, 1, 0) << eitherTrue(0, 0) << eitherTrue() << std::endl;

}

int main()
{
    ZeroEp();
    return 0;
}

最后再看一下官网的例子:

#include 
#include 
#include 
#include 
#include 
#include 
 
template
void printer(Args&&... args)
{
    (std::cout << ... << args) << '\n';
}
 
template
void push_back_vec(std::vector& v, Args&&... args)
{
    static_assert((std::is_constructible_v && ...));
    (v.push_back(args), ...);
}
 
// 基于 http://stackoverflow.com/a/36937049 的编译期端序交换 
template
constexpr T bswap_impl(T i, std::index_sequence)
{
    return (((i >> N*CHAR_BIT & std::uint8_t(-1)) << (sizeof(T)-1-N)*CHAR_BIT) | ...);
}
 
template>
constexpr U bswap(T i)
{
    return bswap_impl(i, std::make_index_sequence{});
}
 
int main()
{
    printer(1, 2, 3, "abc");
 
    std::vector v;
    push_back_vec(v, 6, 2, 45, 12);
    push_back_vec(v, 1, 2, 9);
    for (int i : v) std::cout << i << ' ';
 
    static_assert(bswap(0x1234u) ==
                                       0x3412u);
    static_assert(bswap(0x0123456789abcdefull) ==
                                       0xefcdab8967452301ULL);
}

源码面前,了无秘密。

四、总结

曹孟德说:英雄要有“有包藏宇宙之机”。所以语言发展也要如此,要敢于自我革命,不断吸取先进的好的技术进来。不能闭门造车,更不能循守旧,自高自大。“骄傲自满必翻车”。既不要放弃传统的优势,又勇于学习和吸收好的技术和经验,只有这样,c++才能做为一门优秀的语言,一直不断的发展下去。

你可能感兴趣的:(C++11,c++,java,开发语言)