#include <iostream>
#include <functional>

#include <tuple>
#include <iostream>
using std::cout;
using std::endl;
using namespace std::placeholders;

// helpers for tuple unrolling
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; };

// simple function
double foo_fn(int x, float y, double z)
{
  return x + y + z;
}

// structure with memner function to call
struct foo_struct
{
	// member function to be used as a delegate
	double foo_fn(int x, float y, double z)
	{
		return x + y + z;
	}
	// this member function has different signature - but it can be used too
	// please not that argument order is changed too
	double foo_fn_4(int x, double z, float y, long xx)
	{
		return x + y + z + xx;
	}
};

// delegate class that holds as delegate as its params for future call
template <typename Ret, typename ...Args>
struct delayed_call
{
  // tuple can be used as FunctionParamsPack type
  typedef std::tuple<Args...> params_type;
  // std::function as delegate type
  typedef std::function<Ret(Args...)> function_type;
  
  // stored parameters
  params_type params;
  // stored delegate
  function_type func;

  // invocation
  Ret operator()()
  {
    return callFunc(typename gens<sizeof...(Args)>::type());
  }
  // direct invocation
  Ret operator()(Args... args)
  {
    return func(args...);
  }

  // internal invocation with tuple unrolling
  template<int ...S>
  double callFunc(seq<S...>)
  {
    return func(std::get<S>(params) ...);
  }
};

int main(void)
{
  // arguments
  std::tuple<int, float, double> t = std::make_tuple(1, 5, 10);
  // var #1 - you can use simple function as delegate
  delayed_call<double, int,float, double> saved_foo_fn{t, foo_fn};
  foo_struct fs;
  // var #2 - you can use member function as delegate
  delayed_call<double, int,float, double> saved_foo_fn_struct{t, std::bind(&foo_struct::foo_fn, fs, _1, _2, _3)};
  // var #3 - you can use member function with different signature as delegate. bind 0 to xx
  // please not that argument order is changed
  delayed_call<double, int,float, double> saved_foo_fn_struct_4{t, std::bind(&foo_struct::foo_fn_4, fs, _1, _3, _2, 0l)};
  // var #4 - you can use lambda function as delegate
  delayed_call<double, int,float, double> saved_lambda{t, [](int x, float y, double z)
	{
		return x + y + z;
	}
  };
  cout << "saved_foo_fn: " << saved_foo_fn() << endl;
  cout << "saved_foo_fn_struct: " << saved_foo_fn_struct() << endl;
  cout << "saved_foo_fn_struct_4: " << saved_foo_fn_struct_4() << endl;
  cout << "saved_lambda: " << saved_lambda() << endl;
  cout << "direct call with (1,2,3) to a member: " << saved_foo_fn_struct(1, 2, 3) << endl;
}