0

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;
}
Community
  • 1
  • 1
thor
  • 21,418
  • 31
  • 87
  • 173
  • Are you aware that when you're using basic types with no resources (i.e. ints, doubles, std::array, etc.) move is equal to copy? So there can be no move in this particular code snippet. – Mikhail Jan 21 '14 at 09:11

1 Answers1

2

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 ?

Johan
  • 3,728
  • 16
  • 25
  • 1
    I suggest `std::decay` instead of `std::remove_reference` since it also strips cv-qualification to give you a nice clean unqualified type. – Casey Jan 21 '14 at 15:52
  • @Johan Thanks a lot. It's great idea to augment the data. Regarding the changes to the code, is the general rule to change `Tp` to `Tp&&` wherever `t` is declared and `t` to `forward(t)` wherever `t` is used? – thor Jan 23 '14 at 11:38
  • @TingL I would say yes, everytime you do not want copy, I think the duo `Type&&` and `std::forward` will help. Have a look at [Scott Meyers presentation on Universal References](http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Scott-Meyers-Universal-References-in-Cpp11) to check that I am not saying too much bullshit. – Johan Jan 23 '14 at 22:16