#ifndef NETWORKIT_AUXILIARY_FUNCTION_TRAITS_HPP_
#define NETWORKIT_AUXILIARY_FUNCTION_TRAITS_HPP_

#include <tuple>
#include <type_traits>

namespace Aux {

// Code taken from https://functionalcpp.wordpress.com/2013/08/05/function-traits/ and slightly
// modified

template <class F>
struct FunctionTraits;

// function pointer
template <class R, class... Args>
struct FunctionTraits<R (*)(Args...)> : public FunctionTraits<R(Args...)> {};

template <class R, class... Args>
struct FunctionTraits<R(Args...)> {
    using result_type = R;

    static constexpr size_t arity = sizeof...(Args);

    template <size_t N, bool>
    struct arg_impl;
    template <size_t N>
    struct arg_impl<N, false> {};
    template <size_t N>
    struct arg_impl<N, true> {
        using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
    };

    template <size_t N>
    using arg = arg_impl < N,
          N<arity>;
};

// member function pointer
template <class C, class R, class... Args>
struct FunctionTraits<R (C::*)(Args...)> : public FunctionTraits<R(C &, Args...)> {};

// const member function pointer
template <class C, class R, class... Args>
struct FunctionTraits<R (C::*)(Args...) const> : public FunctionTraits<R(C &, Args...)> {};

// member object pointer
template <class C, class R>
struct FunctionTraits<R(C::*)> : public FunctionTraits<R(C &)> {};

// functor
template <class F>
struct FunctionTraits {
private:
    using call_type = FunctionTraits<decltype(&F::operator())>;

public:
    using result_type = typename call_type::result_type;

    static constexpr size_t arity = call_type::arity - 1;

    template <size_t N>
    using arg = typename call_type::template arg<N + 1>;
};

template <class F>
struct FunctionTraits<F &> : public FunctionTraits<F> {};

template <class F>
struct FunctionTraits<F &&> : public FunctionTraits<F> {};

} // namespace Aux

#endif // NETWORKIT_AUXILIARY_FUNCTION_TRAITS_HPP_
