Applying Tuples To Functors and Functions: The Home Stretch (Part III)

Overview

I missed three things that allow the previously posted solution to require half of the functions (3 instead of 6):

  1. it is legal to return the result of an expression whose type is void() if the function itself is a void function,
  2. std::get() is sufficiently overloaded for lvalues and rvalues so some of the uses of std::forward need not be used,
  3. the use of a class to extract the template parameter list can be eliminated if passed as an unused function argument (i.e., to rely on function argument type deduction).

The code presented below applies these changes.

Bryan (a.k.a. Bearded Code Warrior) has updated his blog to of his distinct alternate version of apply_tuple().


Supporting Code

The supporting code to the solution is the same except std::enable_if is no longer needed:

#include <cstddef>
#include <tuple>
#include <type_traits>

//===================================================================

template <std::size_t...>
struct indices { };

//===================================================================

template <
  std::size_t Begin,
  std::size_t End,
  typename Indices
>
struct make_seq_indices_impl;

template <
  std::size_t Begin,
  std::size_t End,
  std::size_t... Indices
>
struct make_seq_indices_impl<Begin, End, indices<Indices...>>
{
  using type =
    typename
      make_seq_indices_impl<Begin+1, End,indices<Indices..., Begin>
    >::type
  ;
};

template <std::size_t End, std::size_t... Indices>
struct make_seq_indices_impl<End, End, indices<Indices...>>
{
  using type = indices<Indices...>;
};

template <std::size_t Begin, std::size_t End>
using make_seq_indices =
  typename make_seq_indices_impl<Begin, End, indices<>>::type;

//===================================================================

template <typename F>
using return_type = typename std::result_of<F>::type;

template <typename Tuple>
constexpr std::size_t tuple_size()
{
  return std::tuple_size<typename std::decay<Tuple>::type>::value;
}

// No definition exists for the next prototype...
template <
  typename Op,
  typename T,
  template <std::size_t...> class I, std::size_t... Indices
>
constexpr auto apply_tuple_return_type_impl(
  Op&& op,
  T&& t,
  I<Indices...>
) ->
  return_type<Op(
    decltype(std::get<Indices>(std::forward<T>(t)))...
  )>
;

// No definition exists for the next prototype...
template <typename Op, typename T>
constexpr auto apply_tuple_return_type(Op&& op, T&& t) ->
  decltype(apply_tuple_return_type_impl(
    op, t, make_seq_indices<0,tuple_size<T>()>{}
  ));


One Function For apply()

The previous version of apply() required two versions: one for void returns and one for non-void. Since C++ permits returning a void expression when the function is void, e.g.,

void f()
{
}

void g()
{
  return f();
}

only one version of apply() is needed to handle both void and non-void versions:

template <typename Op, typename... Args>
inline auto apply(Op&& op, Args&&... args)
  -> return_type<Op(Args...)>
{
  return op( std::forward<Args>(args)... );
}


Two Functions For apply_tuple()

Similarly, the previous version of apply_tuple() required four versions: two for non-void and two for void. Thus, one can reduce the number of functions needed to two from four:

template <
  typename Ret, typename Op, typename T,
  template <std::size_t...> class I, std::size_t... Indices
>
inline Ret apply_tuple(Op&& op, T&& t, I<Indices...>)
{
  return op(std::get<Indices>(std::forward<T>(t))...);
}

template <typename Op, typename Tuple>
inline auto apply_tuple(Op&& op, Tuple&& t)
  -> decltype(apply_tuple_return_type(
    std::forward<Op>(op), std::forward<Tuple>(t)
  ))
{
  return
    apply_tuple<
      decltype(apply_tuple_return_type(
        std::forward<Op>(op), std::forward<Tuple>(t)
      ))
    >(
      std::forward<Op>(op), std::forward<Tuple>(t),
      make_seq_indices<0,tuple_size<Tuple>()>{}
    );
  ;
}

Notice that the indices are passed as an unused third argument for the function template to infer Indices. The C++ compiler will (or should!) eliminate the overhead of passing such a parameter.


Closing Comments

The above version is terse, short, efficient, and substantially easier to understand than any of the previous versions. (My apologies for missing (oops!) the above easy simplications which were pointed out in a comment to my previously posted article.)

The technique used by Bryan mentioned earlier conceptually does the same thing. The difference with Bryan’s solution is how it works. Bryan’s solution recursively unrolls all template arguments and then applies them to the function, whereas, the solutions I’ve presented expands all template arguments all-at-once (i.e., without using recursion to unroll them).