Question

I have written a function to apply a function to a std::tuple as below (based on "unpacking" a tuple to call a matching function pointer). I am concerned that the tuples might be copied around. I have a very basic idea of what move semantics does, and understand concepts like && and rvalue in the string examples commonly found. But I don't know much about how std::forward() and the likes work. And I am not sure how to handle it when there is also packing and variadic programming. (I added a few std::forward and &&'s around and soon get compilation errors.)

Can someone please explain how to make move semantics work for the tuples here? One additional question is, how can I verify (except for visual inspection of code) that move semantic indeed works for the tuples in the code?

Thanks in advance.

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

template<int ...> struct seq {};

template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};

template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };

template <typename R, typename Tp, typename ...FArgs> 
struct t_app_aux {
  template<int ...S>
  R static callFunc(std::function<R (FArgs...)> f,Tp t,seq<S...>) {
    return f(std::get<S>(t) ...);
  }
};

template <typename R, typename Tp, typename ...FArgs>
R t_app(std::function<R (FArgs...)> f, Tp t) {
  static_assert(std::tuple_size<Tp>::value == sizeof...(FArgs), "type error: t_app wrong arity");
  return t_app_aux<R, Tp, FArgs...>::callFunc(f,t,typename gens<sizeof...(FArgs)>::type());
}

int main(void)
{
  std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5);
  std::function<double (int,float,double)> foo1 = [](int x, float y, double z) {
    return x + y + z;
  };
  std::cout <<  t_app(foo1,t) << std::endl;
}
Was it helpful?

Solution

There are copies with your current implementation: http://ideone.com/cAlorb I added a type with some log:

struct foo
{
foo() : _value(0) { std::cout << "default foo" << std::endl; }
foo(int value) : _value(value) { std::cout << "int foo" << std::endl; }
foo(const foo& other) : _value(other._value) { std::cout << "copy foo" << std::endl; }
foo(foo&& other) : _value(other._value) { std::cout << "move foo" << std::endl; }

int _value;
};

And also before/after your application:

std::cout << "Function created" << std::endl;
std::cout << t_app(foo1,t) << std::endl;
std::cout << "Function applied" << std::endl;

It gives:

Function created
copy foo
copy foo
7.2
Function applied

So then, to fix this adding forward is done like this:

template <typename R, typename Tp, typename ...FArgs> 
struct t_app_aux {
  template<int ...S>
  R static callFunc(std::function<R (FArgs...)> f, Tp&& t, seq<S...>) {
    return f(std::get<S>(std::forward<Tp>(t)) ...);
  }
};

template <typename R, typename Tp, typename ...FArgs>
R t_app(std::function<R (FArgs...)> f, Tp&& t) 
{
 static_assert(std::tuple_size<typename std::remove_reference<Tp>::type>::value == sizeof...(FArgs), 
                "type error: t_app wrong arity");

  return t_app_aux<R, Tp, FArgs...>::callFunc(f, std::forward<Tp>(t), typename gens<sizeof...(FArgs)>::type());
}

As you can see it removes unwanted copies: http://ideone.com/S3wF6x

Function created
7.2
Function applied

The only problem was to handle the static_assert because std::tuple_size was called on a std::tuple<>& and it did not work. I used typename std::remove_reference<Tp>::type but maybe there is a clever and more universal way ?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top