Search code examples
c++templatesc++11stdarraystdtuple

"unpack" std::array<T,N> as arguments to function


Here is quite nice (not mine) example how u can expand (or "explode") tuple as arguments to function:

template<int ...I> struct index_tuple_type {
  template<int N> using append = index_tuple_type<I..., N>;
};

template<int N> struct make_index_impl {
  using type = typename make_index_impl<N-1>::type::template append<N-1>;
};

template<> struct make_index_impl<0> { using type = index_tuple_type<>; };

template<int N> using index_tuple = typename make_index_impl<N>::type;

template <typename I, typename ...Args>
struct func_traits;

template <typename R, int ...I, typename ...Args>
struct func_traits<R, index_tuple_type<I...>, Args...> {
  template <typename TT, typename FT>
  static inline R call(TT &&t, FT &&f) {
    return f(std::get<I>(std::forward<TT>(t))...);
  }
};

template<
  typename FT,
  typename ...Args, 
  typename R = typename std::result_of<FT(Args&&...)>::type
>
inline R explode(std::tuple<Args...>& t, FT &&f) {
  return func_traits<R, index_tuple<sizeof...(Args)>, Args...>
    ::call(t, std::forward<FT>(f));
}

then you can use this like so:

void test1(int i, char c) {
  printf("%d %c\n", i, c);
}

int main() {
  std::tuple<int, char> t1{57, 'a'};
  explode(t1, test1);
}

Live version

I was wandering how could you do the same thing with std::array since it quite like tuple. std::get<N> works with std::array so I thought that it would be easy to modify this solution. But something like this doesn't work:

template<
  typename FT,
  typename Arg,
  std::size_t I,
  typename R = typename std::result_of<FT(Arg&&)>::type
>
inline R explode(std::array<Arg, I>& t, FT &&f) {
  return func_traits<R, index_tuple<I>, Arg>::
    call(t, std::forward<FT>(f));
}

void test2(int i1, int i2) {
  printf("%d %d\n", i1, i2);
}

int main() {
  std::array<int, int> t1{1, 2};
  explode(t2, test1);
}

because of the part std::result_of<FT(Arg&&)>::type. The argument type Arg&& is wrong and result_of has no field type. For tuple Args&&... expanded, but now it should be "repeated" I times. Is there a way to do this using result_of so the returned type can be deducted?

Also i was wondering, having the tools to "unpack" tuple and array would it be possible to "unpack" recursively (probably using enable_if) structure like tuple<array<int, 2>, tuple<array<double,3>, ... and so on? Some kind of a tree where tuple and array are branches, and other types are leaves?


Solution

  • // enable argument dependent lookup on `get` call:
    namespace aux {
      using std::get;
      template<size_t N, class T>
      auto adl_get( T&& )->decltype( get<N>(std::declval<T>()) );
    }
    using aux::adl_get;
    template<class F, class TupleLike, size_t...Is>
    auto explode( F&& f, TupleLike&& tup, std::index_sequence<Is...> )
    -> std::result_of_t< F( decltype(adl_get<Is>(std::forward<TupleLike>(tup)))... ) >
    {
      using std::get; // ADL support
      return std::forward<F>(f)( get<Is>(std::forward<TupleLike>(tup))... );
    }
    

    is the first step. std::index_sequence is C++14, but it is easy to implement in C++11.

    The next steps are also easy.

    First, a traits class that dictates what types are tuple-like. I would go ahead and just duck-type use them, but a number of functions and traits classes we are going to use are not SFINAE friendly:

    template<class T>
    struct tuple_like:std::false_type{};
    template<class... Ts>
    struct tuple_like<std::tuple<Ts...>>:std::true_type{};
    template<class... Ts>
    struct tuple_like<std::pair<Ts...>>:std::true_type{};
    template<class T, size_t N>
    struct tuple_like<std::array<T,N>>:std::true_type{};
    

    Next, an overload of explode that only works on tuple_like types:

    template<class F, class TupleLike,
      class TupleType=std::decay_t<TupleLike>, // helper type
      class=std::enable_if_t<tuple_like<TupleType>{}>> // SFINAE tuple_like test
    auto explode( F&& f, TupleLike&& tup )
    -> decltype(
      explode(
        std::declval<F>(),
        std::declval<TupleLike>(), 
        std::make_index_sequence<std::tuple_size<TupleType>{}>{}
      )
    )
    {
       using indexes = std::make_index_sequence<std::tuple_size<TupleType>{}>;
       return explode(
         std::forward<F>(f),
         std::forward<TupleLike>(tup),
         indexes{}
       );
    }
    

    If you lack constexpr support you need to change some {} to ::value .

    The above does the trick for pairs, arrays or tuples. If you want to add support for other tuple-like types, simply add a specialization to tuple_like and ensure std::tuple_size is specialized properly for your type and get<N> is ADL-overloaded (in the type's enclosing namespace).


    std::make_index_sequence is also C++14 but easy to write in C++11.

    template<size_t...>
    struct index_sequence{};
    namespace details {
      template<size_t count, size_t...Is>
      struct mis_helper:mis_helper<count-1, count-1, Is...> {};
      template<size_t...Is>
      struct mis_helper<0,Is...> {
        using type=index_sequence<Is...>;
      };
    }
    template<size_t count>
    using make_index_sequence=typename details::mis_helper<count>::type;
    

    (this is poor QOI for a C++14 library, which should use at least log descent, as it requires O(n) template recursive template instantiations for a list of size n. However, is n is less than a few 100, it won't matter).

    std::enable_if_t<?> is C++14, but in C++11 is just typename std::enable_if<?>::type.