#include <tuple>
#include <experimental/tuple>
#include <type_traits>

#include <iostream>
#include <typeinfo>

template<bool Condition,
         typename Then,
         typename Else>
using if_t = typename std::conditional<
    Condition, Then, Else>::type;


struct Ignore {
  template<typename Tuple>
  static std::tuple<> from(Tuple) {
    return {};
  }
};
template<std::size_t N>
struct Use {
  template<typename Tuple>
  static auto from(Tuple t) {
    return std:: make_tuple(std::get<N>(t));
  }
};


template<
    std::size_t N,
    typename... Args,
    std::size_t... Is>
auto tuple_remove_impl(
    std::tuple<Args...> const & t,
    std::index_sequence<Is...>) {
  return std::tuple_cat(if_t<N == Is, Ignore, Use<Is>>::from(t)...);
}
template<
    std::size_t N,
    typename... Args>
auto tuple_remove (std::tuple<Args...> const & t) {
  return tuple_remove_impl<N>(t, std::index_sequence_for<Args...>{});
}


template<
    std::size_t N,
    typename Extractor,
    typename R,
    typename... Args>
R trampoline (Args... args) {
  auto all = std::make_tuple(std::ref(args)...);
  auto arguments = tuple_remove<N>(all);
  return std::experimental::apply(
    Extractor{}.get_function(std::get<N>(all)),
    arguments);
}

template<std::size_t Num>
struct Multi {
  template<std::size_t I, typename R, typename... Args>
  struct Extract {
    std::function<R(Args...)> & get_function(void * ptr) {
      auto arr = static_cast<std::array<void *, Num> *>(ptr);
      return *(static_cast<std::function<R(Args...)>*>((*arr)[I]));
    }
  };
  template<typename... Fns>
  static void * wrap(Fns &... fns) {
  	static_assert(sizeof...(fns) == Num, "Don't lie!");
  	std::array<void *, Num> arr = { static_cast<void *>(&fns)... };
  	return static_cast<void*>(new std::array<void *, Num>(std::move(arr)));
  }
  static void free_wrap_result(void * ptr) {
  	delete (static_cast<std::array<void *, Num>*>(ptr));
  }
};

struct Single {
  template<typename R, typename... Args>
  struct Extract {
  	std::function<R(Args...)> & get_function(void * ptr) {
  		return *(static_cast<std::function<R(Args...)>*>(ptr));
  	}
  };
  
  template<typename R, typename... Args>
  static void * wrap(std::function<R(Args...)> & fn) {
  	return &fn;
  }
};

void call_one(void (*fn)(int, void *), void * data) {
  fn(21, data);
}

void call_two(void (*fn)(char const *, void *, double), void * data) {
  fn("FOO", data, 3.14);
}

void call_both(void (*fn)(int, void *), void (*gn)(char const *, void*, double), void * data) {
  call_one(fn, data);
  call_two(gn, data);
}

int main() {
	int offset = 21;
	std::function<void(int)> fn = [&offset] (int value) {
		std::cout << "FN: " << (offset + value) << std::endl; };
	std::function<void(char const *, double)> gn = [] (char const * msg, double value) {
		std::cout << "GN: " << msg << " - " << value << std::endl; };
	
	std::cout << "Single test:" << std::endl;
	call_one(trampoline<1, Single::Extract<void,int>, void, int, void *>, Single::wrap(fn));
	
	std::cout << "Multi test:" << std::endl;
	offset = 0;
	auto fns = Multi<2>::wrap(fn, gn);
	call_both(trampoline<1, Multi<2>::Extract<0, void, int>, void, int, void *>,
	          trampoline<1, Multi<2>::Extract<1, void, char const *, double>, void, char const *, void *, double>,
	          fns);
	Multi<2>::free_wrap_result(fns);
}