如何实现可变参数tuple_map操作?

时间:2014-10-07 21:45:42

标签: c++ tuples variadic-templates c++14

我想实现一个函数,它在给定函数的情况下将元组的可变参数列表映射到另一个元组。

它将N-ary函数f应用于从N个元组列表中取出的元素列表(每个元素的大小至少为M),并根据这些应用程序的结果创建一个新的M元素元组。

对于N个元组的列表,每个元组都有M个元素,对std::make_tuple的调用看起来像这个伪代码:

std::make_tuple(
  f(t1_1, t2_1, t3_1, ..., tN_1),
  f(t1_2, t2_2, t3_2, ..., tN_2),
  f(t1_3, t2_3, t3_3, ..., tN_3),
  ...
  f(t1_M, t2_M, t3_M, ..., tN_M)
)

有时,此操作在其他语言中被命名为zipWith

我希望此函数tuple_map具有以下签名:

template<class Tuple1, class... Tuples2, class Function>
auto tuple_map(Tuple1&& tuple1, Tuples&&... tuples, Function f);

我已经找到了采用单个元组的函数的实现:

#include <tuple>
#include <integer_sequence>
#include <type_traits>
#include <utility>

template<class Tuple, class Function, size_t... I>
auto tuple_map_impl(Tuple&& t, Function f, std::index_sequence<I...>)
  -> decltype(
       std::make_tuple(
         f(std::get<I>(std::forward<Tuple>(t)))...
       )
     )
{
  return std::make_tuple(
    f(std::get<I>(std::forward<Tuple>(t)))...
  );
}

template<class Tuple, class Function>
auto tuple_map(Tuple&& t, Function f)
  -> decltype(
       tuple_map_impl(
         std::forward<Tuple>(t),
         f,
         std::make_index_sequence<
           std::tuple_size<std::decay_t<Tuple>>::value
         >()
       )
     )
{
  using indices = std::make_index_sequence<
    std::tuple_size<std::decay_t<Tuple>>::value
  >;
  return tuple_map_impl(std::forward<Tuple>(t), indices());
}

当我在Tuples之外引入另一个参数包(I...)时,会导致问题:

template<class Tuple1, class... Tuples, class Function, size_t... I>
auto tuple_map_impl(Tuple1&& tuple1, Tuples&&... tuples, Function f, std::index_sequence<I...>)
  -> decltype(
       std::make_tuple(
         f(
           std::get<I>(std::forward<Tuple1>(t1)),
           std::get<I>(std::forward<Tuples>(tuples))...
         )...
       )
     )
{
  return std::make_tuple(
    f(
      std::get<I>(std::forward<Tuple>(t)),
      std::get<I>(std::forward<Tuples>(tuples))...
    )...
  );
}

编译错误:

error: mismatched argument pack lengths while expanding ‘get<I>(forward<Tuples>(tuples))’

这是因为我在同一个表达式中使用了两个不同长度的包(ITuples)。

我想不出一种不同的方式来编写这个不会在同一个表达式中使用这两个包的函数。

实施tuple_map的最佳方式是什么?

3 个答案:

答案 0 :(得分:4)

如果我理解您正在尝试正确执行的操作,则此代码似乎可以通过Visual Studio 2013(2013年11月CTP)实现:

#include <iostream>
#include <tuple>
#include <utility>

using namespace std;

// index_sequence implementation since VS2013 doesn't have it yet
template <size_t... Ints> class index_sequence {
public:
  static size_t size() { return sizeof...(Ints); }
};

template <size_t Start, typename Indices, size_t End>
struct make_index_sequence_impl;

template <size_t Start, size_t... Indices, size_t End>
struct make_index_sequence_impl<Start, index_sequence<Indices...>, End> {
  typedef typename make_index_sequence_impl<
      Start + 1, index_sequence<Indices..., Start>, End>::type type;
};

template <size_t End, size_t... Indices>
struct make_index_sequence_impl<End, index_sequence<Indices...>, End> {
  typedef index_sequence<Indices...> type;
};

template <size_t N>
using make_index_sequence =
    typename make_index_sequence_impl<0, index_sequence<>, N>::type;

// The code that actually implements tuple_map
template <size_t I, typename F, typename... Tuples>
auto tuple_zip_invoke(F f, const Tuples &... ts) {
  return f(get<I>(ts)...);
}

template <typename F, size_t... Is, typename... Tuples>
auto tuple_map_impl(F f, index_sequence<Is...>, const Tuples &... ts) {
  return make_tuple(tuple_zip_invoke<Is>(f, ts...)...);
}

template <typename F, typename Tuple, typename... Tuples>
auto tuple_map(F f, const Tuple &t, const Tuples &... ts) {
  return tuple_map_impl(f, make_index_sequence<tuple_size<Tuple>::value>(), t,
                        ts...);
}

int sum(int a, int b, int c) { return a + b + c; }

int main() {
  auto res =
      tuple_map(sum, make_tuple(1, 4), make_tuple(2, 5), make_tuple(3, 6));
  cout << "(" << get<0>(res) << ", " << get<1>(res) << ")\n";
  return 0;
}

输出是:

(6, 15)

答案 1 :(得分:0)

我可能通过考虑映射和捕捉来解决这个问题。使用现有的tuple_map,试试这个:

template <typename ...Tuple, typename F>
auto tuple_map(Tuple && tuple, F f)
{
    return std::tuple_cat(tuple_map(std::forward<Tuple>(tuple), f)...);
}

答案 2 :(得分:0)

你可以像这样使用递归和std::tuple_cat()

template <class Function, class Tuple, size_t... I>
auto tuple_map_impl(Function f, Tuple&& t, std::index_sequence<I...>)
{
    return std::make_tuple(f(std::get<I>(std::forward<Tuple>(t)))...);
}

template <class F, class Tuple, class... Tuples>
auto tuple_map(F f, Tuple&& t, Tuples&&... tuples)
{
    return std::tuple_cat(tuple_map_impl(f, std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{}),
                          tuple_map(f, tuples...));
}

template <class F>
auto tuple_map(F) { return std::make_tuple(); }

Live Demo