#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());
}


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;
};

int main(void)
{
  std::cout << "Tuple creation" << std::endl;
  std::tuple<int, float, foo> t = std::make_tuple(1, 1.2, foo(5));
  std::cout << "Tuple created" << std::endl;
  std::function<double (int,float,foo&)> foo1 = [](int x, float y, foo& z) {
    return x + y + z._value;    
  };

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