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).

4 Replies to “Applying Tuples To Functors and Functions: The Home Stretch (Part III)”

  1. Very nice solution! I wrote an apply function myself some time ago but never really was pleased with it, since I didn't know how to easily enable it to use perfect forwarding (I did overload the functions with lvalue and rvalue ref versions). I do however have to questions: 1) if you use decltype() anyways, why also use std::result_of? 2) how would you make it work for object methods and object constructors?

    1. You're welcome! To answer your questions: (1) With decltype there is no point in using std::result_of. Simply replace it with decltype. (2) The code already works with functors (i.e., types that overload the function call operator) as is. For the code to work with pointer to member functions (PMFs) one should use std::mem_fn (perhaps with std::bind if needed).

  2. I noticed that clang (unlike GCC) produces warnings due to the internal linkage of the two constexpr functions declared without definition. I can't think of any issues if the two uses of constexpr were removed here; and we keep clang quiet 🙂

    Can I also ask a few questions:

    Is there a license associated with your apply_tuple code?
    I believe the code embedded in this blog post is the latest version. Could it be put in a permanent location such as a code repository?
    Does Boost MPL provide anything similar for its tuples?

    1. Thanks! I will be re-examining the apply_tuple code especially with respect to constexpr when I can resume it and do the last tidy-up article in the series as a complete you-need-only-read-this-article. It will work with the latest GCC and clang.

      There are a number of things missing and awkward about std::tuple and apply_tuple() is only one issue. Not to steal future thunder, I have a much larger code base I have been working on that I intend on releasing in the near future but I don't have a planned date yet due to my current schedule. If you like apply_tuple, then you'll probably like my future release even more. The release will be under a license and available as a repo.

      Right now there is no real license other than the copyright on my site (which is restrictive for code). (I should and will write one.) Right now I am extremely busy so I won't be putting it into a code repo just yet –but I will try to find a time opening to write a final apply_tuple wrap-up that has everything in one article with a .zip download and a licence.

Leave a Reply

Your email address will not be published. Required fields are marked *