//--------------------------------------
// utils/traits: Additional type traits
//--------------------------------------
//
//          Copyright kennytm (auraHT Ltd.) 2011.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

/**

``<utils/traits.hpp>`` --- Additional type traits
=================================================

This module provides additional type traits and related functions, missing from
the standard library.

*/

#ifndef TRAITS_HPP_9ALQFEFX7TO
#define TRAITS_HPP_9ALQFEFX7TO 1

#include <cstdlib>
#include <tuple>
#include <functional>
#include <type_traits>

#include <fplus/internal/meta.hpp>

namespace fplus {

// source: https://github.com/kennytm/utils
namespace utils {

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#endif

/**
.. macro:: DECLARE_HAS_TYPE_MEMBER(member_name)

    This macro declares a template ``has_member_name`` which will check whether
    a type member ``member_name`` exists in a particular type.

    Example::

        DECLARE_HAS_TYPE_MEMBER(result_type)

        ...

        printf("%d\n", has_result_type< std::plus<int> >::value);
        // ^ prints '1' (true)
        printf("%d\n", has_result_type< double(*)() >::value);
        // ^ prints '0' (false)
*/
#define DECLARE_HAS_TYPE_MEMBER(member_name) \
    template <typename, typename = void> \
    struct has_##member_name \
    { enum { value = false }; }; \
    template <typename T> \
    struct has_##member_name<T, typename std::enable_if<sizeof(typename T::member_name)||true>::type> \
    { enum { value = true }; };

/**
.. type:: struct utils::function_traits<F>

    Obtain compile-time information about a function object *F*.

    This template currently supports the following types:

    * Normal function types (``R(T...)``), function pointers (``R(*)(T...)``)
      and function references (``R(&)(T...)`` and ``R(&&)(T...)``).
    * Member functions (``R(C::*)(T...)``)
    * ``std::function<F>``
    * Type of lambda functions, and any other types that has a unique
      ``operator()``.
    * Type of ``std::mem_fn`` (only for GCC's libstdc++ and LLVM's libc++).
      Following the C++ spec, the first argument will be a raw pointer.
*/
template <typename T>
struct function_traits
    : public function_traits<decltype(&T::operator())>
{};

namespace xx_impl
{
    template <typename C, typename R, typename... A>
    struct memfn_type
    {
        typedef typename std::conditional<
            std::is_const<C>::value,
            typename std::conditional<
                std::is_volatile<C>::value,
                R (C::*)(A...) const volatile,
                R (C::*)(A...) const
            >::type,
            typename std::conditional<
                std::is_volatile<C>::value,
                R (C::*)(A...) volatile,
                R (C::*)(A...)
            >::type
        >::type type;
    };
}

template <typename ReturnType, typename... Args>
struct function_traits<ReturnType(Args...)>
{
    /**
    .. type:: type result_type

        The type returned by calling an instance of the function object type *F*.
    */
    typedef ReturnType result_type;

    /**
    .. type:: type function_type

        The function type (``R(T...)``).
    */
    typedef ReturnType function_type(Args...);

    /**
    .. type:: type member_function_type<OwnerType>

        The member function type for an *OwnerType* (``R(OwnerType::*)(T...)``).
    */
    template <typename OwnerType>
    using member_function_type = typename xx_impl::memfn_type<
        typename std::remove_pointer<typename std::remove_reference<OwnerType>::type>::type,
        ReturnType, Args...
    >::type;

    /**
    .. data:: static const size_t arity

        Number of arguments the function object will take.
    */
    enum { arity = sizeof...(Args) };

    /**
    .. type:: type arg<n>::type

        The type of the *n*-th argument.
    */
    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
    };
};

template <typename ReturnType, typename... Args>
struct function_traits<ReturnType(*)(Args...)>
    : public function_traits<ReturnType(Args...)>
{};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...)>
    : public function_traits<ReturnType(Args...)>
{
    typedef ClassType& owner_type;
};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
    : public function_traits<ReturnType(Args...)>
{
    typedef const ClassType& owner_type;
};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) volatile>
    : public function_traits<ReturnType(Args...)>
{
    typedef volatile ClassType& owner_type;
};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const volatile>
    : public function_traits<ReturnType(Args...)>
{
    typedef const volatile ClassType& owner_type;
};

template <typename FunctionType>
struct function_traits<std::function<FunctionType>>
    : public function_traits<FunctionType>
{};

#if defined(_GLIBCXX_FUNCTIONAL)
#define MEM_FN_SYMBOL_XX0SL7G4Z0J std::_Mem_fn
#elif defined(_LIBCPP_FUNCTIONAL)
#define MEM_FN_SYMBOL_XX0SL7G4Z0J std::__mem_fn
#endif

#ifdef MEM_FN_SYMBOL_XX0SL7G4Z0J

template <typename R, typename C>
struct function_traits<MEM_FN_SYMBOL_XX0SL7G4Z0J<R C::*>>
    : public function_traits<R(C*)>
{};
template <typename R, typename C, typename... A>
struct function_traits<MEM_FN_SYMBOL_XX0SL7G4Z0J<R(C::*)(A...)>>
    : public function_traits<R(C*, A...)>
{};
template <typename R, typename C, typename... A>
struct function_traits<MEM_FN_SYMBOL_XX0SL7G4Z0J<R(C::*)(A...) const>>
    : public function_traits<R(const C*, A...)>
{};
template <typename R, typename C, typename... A>
struct function_traits<MEM_FN_SYMBOL_XX0SL7G4Z0J<R(C::*)(A...) volatile>>
    : public function_traits<R(volatile C*, A...)>
{};
template <typename R, typename C, typename... A>
struct function_traits<MEM_FN_SYMBOL_XX0SL7G4Z0J<R(C::*)(A...) const volatile>>
    : public function_traits<R(const volatile C*, A...)>
{};

#undef MEM_FN_SYMBOL_XX0SL7G4Z0J
#endif

template <typename T>
struct function_traits<T&> : public function_traits<T> {};
template <typename T>
struct function_traits<const T&> : public function_traits<T> {};
template <typename T>
struct function_traits<volatile T&> : public function_traits<T> {};
template <typename T>
struct function_traits<const volatile T&> : public function_traits<T> {};
template <typename T>
struct function_traits<T&&> : public function_traits<T> {};
template <typename T>
struct function_traits<const T&&> : public function_traits<T> {};
template <typename T>
struct function_traits<volatile T&&> : public function_traits<T> {};
template <typename T>
struct function_traits<const volatile T&&> : public function_traits<T> {};


#define FORWARD_RES_8QR485JMSBT \
    typename std::conditional< \
        std::is_lvalue_reference<R>::value, \
        T&, \
        typename std::remove_reference<T>::type&& \
    >::type

/**
.. function:: auto utils::forward_like<Like, T>(T&& t) noexcept

    Forward the reference *t* like the type of *Like*. That means, if *Like* is
    an lvalue (reference), this function will return an lvalue reference of *t*.
    Otherwise, if *Like* is an rvalue, this function will return an rvalue
    reference of *t*.

    This is mainly used to propagate the expression category (lvalue/rvalue) of
    a member of *Like*, generalizing ``std::forward``.
*/
template <typename R, typename T>
FORWARD_RES_8QR485JMSBT forward_like(T&& input) noexcept
{
    return static_cast<FORWARD_RES_8QR485JMSBT>(input);
}

#undef FORWARD_RES_8QR485JMSBT

/**
.. type:: struct utils::copy_cv<From, To>

    Copy the CV qualifier between the two types. For example,
    ``utils::copy_cv<const int, double>::type`` will become ``const double``.
*/
template <typename From, typename To>
struct copy_cv
{
private:
    typedef typename std::remove_cv<To>::type raw_To;
    typedef typename std::conditional<std::is_const<From>::value,
                                      const raw_To, raw_To>::type const_raw_To;
public:
    /**
    .. type:: type type

        Result of cv-copying.
    */
    typedef typename std::conditional<std::is_volatile<From>::value,
                                      volatile const_raw_To, const_raw_To>::type type;
};

/**
.. type:: struct utils::pointee<T>

    Returns the type by derefering an instance of *T*. This is a generalization
    of ``std::remove_pointer``, that it also works with iterators.
*/
template <typename T>
struct pointee
{
    /**
    .. type:: type type

        Result of dereferencing.
    */
    typedef typename std::remove_reference<decltype(*std::declval<T>())>::type type;
};

/**
.. function:: std::add_rvalue_reference<T>::type utils::rt_val<T>() noexcept

    Returns a value of type *T*. It is guaranteed to do nothing and will not
    throw a compile-time error, but using the returned result will cause
    undefined behavior.
*/
template <typename T>
typename std::add_rvalue_reference<T>::type rt_val() noexcept
{
    return std::move(*static_cast<T*>(nullptr));
}

#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

}
}

namespace fplus
{
namespace internal
{
template <typename>
struct is_std_function : std::false_type
{
};

template <typename T>
struct is_std_function<std::function<T>> : std::true_type
{
};

// Those traits are needed to not perform arity checks on a generic-lambd
// or a templated/overloaded operator()
template <typename T, typename = void>
struct has_function_traits : std::false_type
{
};

// There is a bug with GCC 7 when a std::function is passed as T.
// It produces an ambiguous call between this one and the std::function overload
// It's related to our void_t implementation, the C++14 compatible version does not
// work, whereas the C++17 one does...
//
// So, help GCC a bit with is_std_function
template <typename T>
struct has_function_traits<T,
                           std::enable_if_t<!is_std_function<T>::value,
                                            void_t<decltype(&T::operator())>>>
    : std::true_type
{
};

template <typename ReturnType, typename... Args>
struct has_function_traits<ReturnType(Args...)> : std::true_type
{
};

template <typename ReturnType, typename... Args>
struct has_function_traits<ReturnType (*)(Args...)> : std::true_type
{
};

template <typename ReturnType, typename ClassType, typename... Args>
struct has_function_traits<ReturnType (ClassType::*)(Args...)> : std::true_type
{
};

template <typename ReturnType, typename ClassType, typename... Args>
struct has_function_traits<ReturnType (ClassType::*)(Args...) const>
    : std::true_type
{
};

template <typename ReturnType, typename ClassType, typename... Args>
struct has_function_traits<ReturnType (ClassType::*)(Args...) volatile>
    : std::true_type
{
};

template <typename ReturnType, typename ClassType, typename... Args>
struct has_function_traits<ReturnType (ClassType::*)(Args...) const volatile>
    : std::true_type
{
};

template <typename ReturnType, typename ClassType, typename... Args>
struct has_function_traits<ReturnType (ClassType::*)(Args...)&> : std::true_type
{
};

template <typename ReturnType, typename ClassType, typename... Args>
struct has_function_traits<ReturnType (ClassType::*)(Args...) const &>
    : std::true_type
{
};

template <typename ReturnType, typename ClassType, typename... Args>
struct has_function_traits<ReturnType (ClassType::*)(Args...) volatile&>
    : std::true_type
{
};

template <typename ReturnType, typename ClassType, typename... Args>
struct has_function_traits<ReturnType (ClassType::*)(Args...) const volatile&>
    : std::true_type
{
};

template <typename ReturnType, typename ClassType, typename... Args>
struct has_function_traits<ReturnType (ClassType::*)(Args...) &&>
    : std::true_type
{
};

template <typename ReturnType, typename ClassType, typename... Args>
struct has_function_traits<ReturnType (ClassType::*)(Args...) const &&>
    : std::true_type
{
};

template <typename ReturnType, typename ClassType, typename... Args>
struct has_function_traits<ReturnType (ClassType::*)(Args...) volatile&&>
    : std::true_type
{
};

template <typename ReturnType, typename ClassType, typename... Args>
struct has_function_traits<ReturnType (ClassType::*)(Args...) const volatile&&>
    : std::true_type
{
};

template <typename FunctionType>
struct has_function_traits<std::function<FunctionType>> : std::true_type
{
};
}
}

#endif
