Reputation: 22500
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;
}
Upvotes: 0
Views: 1205
Reputation: 3778
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 ?
Upvotes: 2