#include <array>
#include <cassert>
#include <iostream>
#include <functional>
#include <tuple>
#include <type_traits>
/* std::invoke (since C++17) */
#define RETURN(...) -> decltype(__VA_ARGS__) { return __VA_ARGS__; }
template <typename F, typename... As>
auto invoke(F &&f, As &&... as)
RETURN(std::forward<F>(f)(std::forward<As>(as)...));
template <typename B, typename T, typename D>
auto invoke(T B::*pmv, D &&d) RETURN(std::forward<D>(d).*pmv);
template <typename Pmv, typename Ptr>
auto invoke(Pmv pmv, Ptr &&ptr) RETURN((*std::forward<Ptr>(ptr)).*pmv);
template <typename B, typename T, typename D, typename... As>
auto invoke(T B::*pmf, D &&d, As &&... as)
RETURN((std::forward<D>(d).*pmf)(std::forward<As>(as)...));
template <typename Pmf, typename Ptr, typename... As>
auto invoke(Pmf pmf, Ptr &&ptr, As &&... as)
RETURN(((*std::forward<Ptr>(ptr)).*pmf)(std::forward<As>(as)...));
#undef RETURN
/* std::apply (since C++17) */
template <typename F, typename Args, size_t... Is>
decltype(auto) apply_impl(F &&f, Args &&args, std::index_sequence<Is...>) {
return invoke(std::forward<F>(f), std::get<Is>(std::forward<Args>(args))...);
}
template <typename F, typename Args>
decltype(auto) apply(F &&f, Args &&args) {
return apply_impl(
std::forward<F>(f),
std::forward<Args>(args),
std::make_index_sequence<std::tuple_size<std::decay_t<Args>>::value>());
}
/* function_arity */
template <typename F>
struct function_arity;
template <typename R, typename... Args>
struct function_arity<R (Args...)>
: std::integral_constant<std::size_t, sizeof...(Args)> {};
template <typename R, typename... Args>
struct function_arity<R (*)(Args...)> : function_arity<R (Args...)> {};
template <typename R, typename... Args>
struct function_arity<R (&)(Args...)> : function_arity<R (Args...)> {};
template <typename R, typename C, typename... Args>
struct function_arity<R (C::*)(Args...) const> : function_arity<R (Args...)> {};
template <typename R, typename C, typename... Args>
struct function_arity<R (C::*)(Args...)> : function_arity<R (Args...)> {};
template <typename C>
struct function_arity : function_arity<decltype(&C::operator())> {};
/* make_integer_range */
template <typename T, typename U, T Begin>
struct make_integer_range_impl;
template <typename T, T... Ints, T Begin>
struct make_integer_range_impl<T, std::integer_sequence<T, Ints...>, Begin> {
using type = std::integer_sequence<T, Begin + Ints...>;
};
template <class T, T Begin, T End>
using make_integer_range =
typename make_integer_range_impl<T,
std::make_integer_sequence<T, End - Begin>,
Begin>::type;
template <std::size_t Begin, std::size_t End>
using make_index_range = make_integer_range<std::size_t, Begin, End>;
/* slice */
template <std::size_t... Is, std::size_t... Js>
constexpr decltype(auto) slice_impl(std::index_sequence<Is...>,
std::index_sequence<Js...>) {
using array_t = std::array<std::size_t, sizeof...(Is)>;
return std::index_sequence<std::get<Js>(array_t{{Is...}})...>();
}
template <std::size_t Begin, std::size_t End, std::size_t... Is>
constexpr decltype(auto) slice(std::index_sequence<Is...> is) {
return slice_impl(is, make_index_range<Begin, End>());
}
/* partial_sum */
template <typename Is>
struct partial_sum;
template <typename Is>
using partial_sum_t = typename partial_sum<Is>::type;
template <>
struct partial_sum<std::index_sequence<>> {
using type = std::index_sequence<>;
};
template <std::size_t I, std::size_t... Is>
struct partial_sum<std::index_sequence<I, Is...>> {
private:
template <typename Js>
struct impl;
template <std::size_t... Js>
struct impl<std::index_sequence<Js...>> {
using type = std::index_sequence<I, Js + I...>;
};
public:
using type = typename impl<partial_sum_t<std::index_sequence<Is...>>>::type;
};
/* guarded_apply_impl */
template <typename R>
struct guarded_invoke_impl {
template <typename F, typename... Args>
decltype(auto) operator()(F &&f, Args &&...args) const {
return invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
};
template <>
struct guarded_invoke_impl<void> {
template <typename F, typename... Args>
auto operator()(F &&f, Args &&... args) const {
invoke(std::forward<F>(f), std::forward<Args>(args)...);
return nullptr;
}
};
template <typename T>
struct guarded_invoke_impl<T &> {
template <typename F, typename... Args>
auto operator()(F &&f, Args &&...args) const {
return std::ref(invoke(std::forward<F>(f), std::forward<Args>(args)...));
}
};
template <typename F, typename... Args>
decltype(auto) guarded_invoke(F &&f, Args &&... args) {
using R = decltype(invoke(std::forward<F>(f), std::forward<Args>(args)...));
return guarded_invoke_impl<R>{}(std::forward<F>(f),
std::forward<Args>(args)...);
}
template <typename F, typename Args, size_t... Is>
static decltype(auto) guarded_apply_impl(F &&f,
Args &&args,
std::index_sequence<Is...>) {
return guarded_invoke(std::forward<F>(f),
std::get<Is>(std::forward<Args>(args))...);
}
/* call */
template <typename Fs>
struct call_t {
explicit call_t(Fs &&fs) : fs_(std::move(fs)) {}
template <typename... Args>
auto operator()(Args &&... args) const && {
using function_arities = typename get_function_arities<Fs>::type;
auto partial_sum = partial_sum_t<function_arities>{};
auto tuple = std::forward_as_tuple(std::forward<Args>(args)...);
auto is = slice<0, std::tuple_size<Fs>{}>(partial_sum);
auto js = slice<1, std::tuple_size<Fs>{} + 1>(partial_sum);
return apply(
impl<decltype(tuple), decltype(is), decltype(js)>{std::move(tuple)},
std::move(fs_));
}
private:
template <typename T>
struct get_function_arities;
template <typename... Gs>
struct get_function_arities<std::tuple<Gs...>> {
using type = std::index_sequence<0, function_arity<std::decay_t<Gs>>{}...>;
};
template <typename Args, typename Is, typename Js>
struct impl;
template <typename Args, std::size_t... Is, std::size_t... Js>
struct impl<Args, std::index_sequence<Is...>, std::index_sequence<Js...>> {
template <typename... Gs>
auto operator()(Gs &&... gs) const {
using tuple_t = decltype(
std::make_tuple(guarded_apply_impl(std::forward<Gs>(gs),
std::move(args_),
make_index_range<Is, Js>{})...));
// The use of braced-initializer syntax rather than `make_tuple` is
// necessary to guarantee left-to-right order of evaluation.
return tuple_t{guarded_apply_impl(std::forward<Gs>(gs),
std::move(args_),
make_index_range<Is, Js>{})...};
}
Args args_;
}; // impl
Fs fs_;
}; // call_t
template <typename... Fs>
auto call(Fs &&... fs) {
auto tuple = std::forward_as_tuple(std::forward<Fs>(fs)...);
return call_t<decltype(tuple)>{std::move(tuple)};
}
void E() { std::cout << "E()" << std::endl; }
int F(int x, int y) {
std::cout << "F(" << x << ", " << y << ')' << std::endl;
return 101;
}
int &G(int &x) {
std::cout << "G(" << x << ')' << std::endl;
return x;
}
int main() {
{
int n = 42;
auto result = call(E, F, G)(1, 2, n);
assert(result == std::make_tuple(nullptr, 101, 42));
}
{
auto e = []() { std::cout << "e()" << std::endl; };
auto f = [](int x, int y) {
std::cout << "f(" << x << ", " << y << ')' << std::endl;
return 101;
};
auto g = [](int &x) -> int & {
std::cout << "g(" << x << ')' << std::endl;
return x;
};
int n = 42;
auto result = call(e, f, g)(1, 2, n);
assert(result == std::make_tuple(nullptr, 101, 42));
}
}