如何在C ++ 11中使用可变参数模板生成左联想表达式(又称左折)?

时间:2019-02-19 06:59:10

标签: c++ c++11 templates variadic-templates fold

我想使用c ++模板通过二进制操作聚合(折叠)多个参数。

这样的模板可以如下使用:

fold<add>(100,10,5)扩展为add(add(100, 10), 5)

上面显示的特定扩展名是“左折”。扩展add(100, add(10, 5))是“右折”。假设add函数执行简单的整数加法运算,则右折和左折都会产生相同的结果115。

但是考虑执行整数除法(div)的函数div(a,b)=a/b。在这种情况下,关联性很重要,左右折叠会产生不同的结果:

fold_left<div>(100,10,5)  --> div(div(100, 10), 5) --> div(10, 5) -->  2
fold_right<div>(100,10,5) --> div(100, div(10, 5)) --> div(100, 2) --> 50

使用可变参数模板生成右关联版本(fold_right)很简单,但是我无法弄清楚如何生成左关联版本(fold_left) 。下面的fold_left的尝试实现会导致编译器错误:

#include <iostream>

template <typename T> using binary_op = T(*)(const T& a, const T& b);

// The terminal (single-argument) cases of the variadic functions defined later. 
template<typename T, binary_op<T> Operation> inline T fold_right(const T& t) { return t; }
template<typename T, binary_op<T> Operation> inline T fold_left(const T& t) { return t; }

// Combines arguments using right-associative operation
// i.e. fold_right<T,op>(A,B,C) --> op(A, op(B,C))
template<typename T, binary_op<T> Operation, typename ... Rest> 
inline T fold_right(const T& t, Rest... rest) {
    return Operation(t, fold_right<T, Operation>(rest...));
}

// Combines arguments using left-associative operation
// i.e. fold_left<T,op>(A,B,C) --> op(op(A,B), C)
template<typename T, binary_op<T> Operation, typename ... Rest> 
inline T fold_left(Rest... rest, const T& t) {
    return Operation(fold_left<T, Operation>(rest...), t);
}

inline int add(const int& a, const int& b) { return a+b; }
inline int div(const int& a, const int& b) { return a/b; }

int main() {
    std::cout << fold_right<int,div>(100,10,5) //  (100 / (10 / 5))  = 50
              << "\n"
              << fold_left<int,div>(100,10,5)  //  Compiler error!
              << std::endl;
    return 0;
}

如何使用可变参数模板(在c ++ 11中)正确实现fold_left

我认为本质上可以归结为能够“弹出”参数包中的最后一个参数,我尝试在上面的left_fold模板中这样做,但是正如我所说,这导致了编译器的出现错误。

注意:在本问题中,我以简单的算术运算和整数为例,但是答案应该足够通用,以使用任意函数处理对象的聚合(假设它返回与其对象相同类型的对象)参数)。

注2:对于熟悉c ++ 17的人,可以使用 fold expressions 用二元运算符产生左右折叠。但是这些在c ++ 11中不可用。

作为一个相关问题:上面的模板要求明确指定类型T,如fold_right<int,div>(...)中所示。是否有某种方法可以制定模板,以便仅需要操作,例如fold_right<div>(...)。我认为可以推断类型T,但是我看不到一种方法来对模板参数进行排序以将binary_op<>放在首位。

谢谢!

3 个答案:

答案 0 :(得分:10)

左侧的参数包有问题。更好地将其重新实现为右侧的参数包:

template<typename T, binary_op<T> Operation> 
inline T fold_left(const T& t) { return t; }

template<typename T, binary_op<T> Operation, typename ... Rest>
inline T fold_left(const T& a, const T& b, Rest... rest) {
    return fold_left<T, Operation>(Operation(a,b), rest...);
}

答案 1 :(得分:2)

迈克尔回答了您的第一个问题。 第二个可能有不同的答案。我更喜欢的方法是将您的操作定义为具有模板成员的函子:

#include <type_traits>
struct homogene_add{
    template<typename T>
    T operator()(T const& lhs, T const& rhs){/*...*/}
};

struct mixed_add{
    template<typename L, typename R>
    std::common_type<L,R>::type
    operator()(L const& lhs, R const& rhs){/*...*/}
};

template<typename binary_op, typename ... Args> 
std::common_type<Args...>::type
fold_right(const Args&... args);

template<typename binary_op, typename First, typename ... Args>
std::common_type<First, Args...>::type
fold_right(const First& init, const Args&... args) {
    binary_op op;
    return op(init, fold_right<binary_op>(args...));
};

template<typename binary_op, typename First>
const First& fold_right(const First& init) {
    return init;
};

简历的资格和价值的正确性,我去看OP。

答案 2 :(得分:0)

这是非跟踪函数参数包的魔力:它们仅从显式提供的模板参数推导出来。

这意味着rest...fold_left<int,div>(100,10,5)调用中为空。因此,您的函数只有一个参数,而不是3。