【问题标题】:how to write a fold /sum function for C++ tuple?如何为 C++ 元组编写折叠/求和函数?
【发布时间】:2018-04-23 06:51:39
【问题描述】:

我想为std::tuple 编写一个fold 函数,它可以计算例如给定元组中所有元素的总和(或乘积)。例如,给定

std::tuple<int,double> t = std::make_tuple(1,2);

我想计算

auto s = sumT(t); //giving 3

我尝试过但无法编译下面的模板编程 (c++11/1z) 代码。我还尝试为我的其他问题 (How to perform tuple arithmetic in C++ (c++11/c++17)?) 调整已接受的答案,但在这种情况下无法弄清楚如何使用 std::index_sequence

我遇到的问题是:

1) 我不知道类型,例如如何使用第一个元素的类型作为返回类型。目前,我在模板中使用了_res 类型,但我不知道这是否会阻止 c++ 的自动类型推断。

2) 我想在不使用显式初始元素0 的情况下对此进行编程,以便可以将其用于其他类型的fold 操作。

目前,递归在最后一个元素处结束。我想在_size - 1 处结束递归,这样我就可以直接对最后一个元素执行操作,而无需诉诸0

我下面的代码尝试通过递归来做到这一点。但我不太了解模板编程,以及循环如何处理元组。

有人可以帮助修复代码或提出更好的解决方案吗?

到目前为止我的代码是:

#include <tuple>
#include <iostream>
#include <functional>

// helper class for fold operations
template<typename Op,typename _res, typename _Tp, size_t _i, size_t _size>
struct _tuple_fold  {
    static constexpr _res _op(Op const & op, const _Tp& _t) {
      return _res(op(std::get<_i>(_t),
                _tuple_fold<Op, _res, _Tp, _i + 1, _size>::_op(op,_t) ));
    }
};

template<typename Op,typename _res,typename _Tp, size_t _size>
struct _tuple_fold<Op, _res,_Tp, _size, _size> {
  static constexpr _res _op(Op const &, const _Tp&) { return 0; }
};

template <typename ... Ts>
auto sumT (std::tuple<Ts...> const & t1)  {
  return _tuple_fold::_op(std::plus<>{}, t1);
}

int main () {
  std::tuple<int,double> t = std::make_tuple(1,2);
  auto s = sumT(t);
  std::cout << s << std::endl;
}

使用g++ -std=c++17 tuple_sum.cpp编译的错误信息:

tuple_sum.cpp: In function ‘auto sumT(const std::tuple<_Elements ...>&)’:
tuple_sum.cpp:21:10: error: ‘template<class Op, class _res, class _Tp, long unsigned int _i, long unsigned int _size> struct _tuple_fold’ used without template parameters
   return _tuple_fold::_op(std::plus<>{}, t1);
          ^
tuple_sum.cpp: In function ‘int main()’:
tuple_sum.cpp:27:19: error: ‘void s’ has incomplete type
    auto s = sumT(t);
                   ^

我不确定如何在调用站点上为_tuple_fold 提供类型参数,尤其是std::plus 的类型。

【问题讨论】:

标签: c++ c++11 c++17 stdtuple


【解决方案1】:

注意在c++17中我们可以apply()fold

auto t = std::make_tuple( 1, 2. );
auto sum = std::apply([]( auto... v ){ return ( v + ... ); }, t );

这适用于任何类似元组的类型,并遵循开箱即用的“+”的通常提升/转换规则(这可能是可取的,也可能不是可取的)。在上面我通过值传递,因为我们正在处理算术类型,但你当然可以应用你喜欢的转发策略......

【讨论】:

  • 当您知道并且可以对折叠运算符 + 进行硬编码时,这非常有用。
【解决方案2】:

谢天谢地,C++17 中的 if constexpr 允许我们通过引入部分专用的辅助结构来避免使事情复杂化,并且可以轻松地在我们想要的任何条件下终止递归:

#include <functional>
#include <iostream>
#include <tuple>
#include <type_traits>

template <size_t index, class Op, class... Ts>
constexpr auto tuple_fold(Op op, const std::tuple<Ts...>& t) {
    if constexpr(index == sizeof...(Ts) - 1) {
        return std::get<index>(t);
    } else {
        return op(std::get<index>(t), tuple_fold<1 + index>(op, t));
    }
}

template <typename ... Ts>
constexpr auto sumT (std::tuple<Ts...> const & t1)  {
  return tuple_fold<0>(std::plus<>{}, t1);
}

int main () {
  std::tuple<int,double> t = {1, 2.0};
  auto s = sumT(t);
  static_assert(std::is_same_v<decltype(s), double>);
  std::cout << s << std::endl;
}

Coliru 链接:http://coliru.stacked-crooked.com/a/1e7051b8652fb942

这将执行右折叠,a + (b + (c + ...)),但如果您愿意,可以很容易地将其重写为执行左折叠。

【讨论】:

    【解决方案3】:

    我们可以利用 的左右折叠内置函数来折叠任何二进制操作。

    template<class F, class Lhs=void>
    struct invoke_by_times_t {
      F& f;
      Lhs lhs;
      template<class Rhs>
      auto operator*( Rhs&& rhs )&&
      ->invoke_by_times_t<F, std::invoke_result_t< F&, Lhs, Rhs >>
      {
        return {
          f,
          f(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs))
        };
      }
    };
    
    template<class F>
    struct invoke_by_times_t<F, void> {
      F& f;
      template<class Rhs>
      invoke_by_times_t<F, Rhs> operator*( Rhs&& rhs )&&{
        return {f, std::forward<Rhs>(rhs)};
      }
    };
    
    template<class F>
    auto fold_over( F&& f ) {
      return [f=std::forward<F>(f)](auto&&...args)mutable{
        return ( invoke_by_times_t<F>{f}*...*decltype(args)(args) ).lhs;
      };
    }
    

    现在给定任何二进制函数,我们可以创建一个函数对象,它可以折叠它而无需进行任何递归。

    std::apply 一起完成。

    template <typename ... Ts>
    auto sumT (std::tuple<Ts...> const & t1)  {
      return std::apply( fold_over(std::plus<>{}), t1);
    }
    

    Live example

    这是一个左折叠。正确折叠只涉及更改fold_over 函数。如果您尝试将一个空包传递给它,它将无法编译。如果你传递一个元素,它会返回那个元素。

    【讨论】:

      【解决方案4】:
      #include <tuple>
      #include <utility>
      
      namespace detail {
      
      template<class F, class T>
      struct foldable : std::pair<const F&, T> {
          using std::pair<const F&, T>::pair;
      
          template<class V>
          constexpr decltype(auto) operator&&(foldable<F, V>&& x) {
              return detail::foldable {
                  this->first,
                  this->first(this->second, x.second),
              };
          }
      };
      
      template<class F, class T> foldable(const F&, T&&) -> foldable<F, T>;
      
      }
      
      // Folds left a parameter pack
      template<class F, class... Args>
      constexpr decltype(auto) fold_left_pack(const F& f, Args&&... args) {
          static_assert(sizeof...(Args) > 0, "Cannot fold an empty pack.");
          return (... && detail::foldable { f, std::forward<Args>(args) }).second;
      }
      
      // Folds right a parameter pack
      template<class F, class... Args>
      constexpr decltype(auto) fold_right_pack(const F& f, Args&&... args) {
          static_assert(sizeof...(Args) > 0, "Cannot fold an empty pack.");
          return (detail::foldable { f, std::forward<Args>(args) } && ...).second;
      }
      
      // Folds left a tuple
      template<class F, class Tuple>
      constexpr decltype(auto) fold_left(const F& f, Tuple&& tuple) {
          return std::apply(
              [&](auto&&... args) -> decltype(auto) {
                  return fold_left_pack(f, std::forward<decltype(args)>(args)...);
              },
              std::forward<Tuple>(tuple));
      }
      
      // Folds right a tuple
      template<class F, class Tuple>
      constexpr decltype(auto) fold_right(const F& f, Tuple&& tuple) {
          return std::apply(
              [&](auto&&... args) -> decltype(auto) {
                  return fold_right_pack(f, std::forward<decltype(args)>(args)...);
              },
              std::forward<Tuple>(tuple));
      }
      

      测试:

      constexpr auto divide = [](auto x, auto y) { return x / y; };
      constexpr auto tuple = std::make_tuple(8, 4, 2);
      static_assert(1 == fold_left(divide, tuple));
      static_assert(4 == fold_right(divide, tuple));
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-02-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-19
        • 2011-07-07
        • 2010-11-25
        相关资源
        最近更新 更多