# dlib C++ Library - invoke.h

    // Copyright (C) 2021  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_INVOKE_Hh_
    #define DLIB_INVOKE_Hh_

    #include <functional>
    #include <type_traits>
    #include "utility.h"

    namespace dlib
    {

    // ----------------------------------------------------------------------------------------

        namespace detail
        {
            template< typename T >
            struct is_reference_wrapper : std::false_type {};
            template< typename U >
            struct is_reference_wrapper<std::reference_wrapper<U>> : std::true_type {};

            template <
                typename Base,
                typename T,
                typename Derived,
                typename... Args
            >
            constexpr auto invoke_(
                T Base::*pmf, //pointer to member function
                Derived&& ref,
                Args&&... args
            )
            noexcept(noexcept((std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...)))
            -> typename std::enable_if<
                    std::is_function<T>::value &&
                    std::is_base_of<Base, typename std::decay<Derived>::type>::value,
                    decltype((std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...))
               >::type
            {
                return (std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...);
            }

            template<
                typename Base,
                typename T,
                typename RefWrap,
                typename... Args
            >
            constexpr auto invoke_(
                T Base::*pmf, //pointer to member function
                RefWrap&& ref,
                Args&&... args
            )
            noexcept(noexcept((ref.get().*pmf)(std::forward<Args>(args)...)))
            -> typename std::enable_if<
                    std::is_function<T>::value &&
                    is_reference_wrapper<typename std::decay<RefWrap>::type>::value,
                    decltype((ref.get().*pmf)(std::forward<Args>(args)...))>::type
            {
                return (ref.get().*pmf)(std::forward<Args>(args)...);
            }

            template<
                typename Base,
                typename T,
                typename Ptr,
                typename... Args
            >
            constexpr auto invoke_(
                T Base::*pmf, //pointer to member function
                Ptr&& ptr,
                Args&&... args
            )
            noexcept(noexcept(((*std::forward<Ptr>(ptr)).*pmf)( std::forward<Args>( args )...)))
            -> typename std::enable_if<
                    std::is_function<T>::value &&
                    !is_reference_wrapper<typename std::decay<Ptr>::type>::value &&
                    !std::is_base_of<Base, typename std::decay<Ptr>::type>::value,
                    decltype(((*std::forward<Ptr>(ptr)).*pmf)(std::forward<Args>(args)...))>::type
            {
                return ((*std::forward<Ptr>(ptr)).*pmf)( std::forward<Args>( args )...);
            }

            template<
                typename Base,
                typename T,
                typename Derived
            >
            constexpr auto invoke_(
                T Base::*pmd, //pointer to member data
                Derived&& ref
            )
            noexcept(noexcept(std::forward<Derived>(ref).*pmd))
            -> typename std::enable_if<
                    std::is_object<T>::value &&
                    std::is_base_of<Base, typename std::decay<Derived>::type>::value,
                    decltype(std::forward<Derived>(ref).*pmd)>::type
            {
                return std::forward<Derived>(ref).*pmd;
            }

            template<
                typename Base,
                typename T,
                typename RefWrap
            >
            constexpr auto invoke_(
                T Base::*pmd, //pointer to member data
                RefWrap&& ref
            )
            noexcept(noexcept(ref.get().*pmd))
            -> typename std::enable_if<
                    std::is_object<T>::value &&
                    is_reference_wrapper<typename std::decay<RefWrap>::type>::value,
                    decltype(ref.get().*pmd)>::type
            {
                return ref.get().*pmd;
            }

            template<
                typename Base,
                typename T,
                typename Ptr
            >
            constexpr auto invoke_(
                T Base::*pmd, //pointer to member data
                Ptr&& ptr
            )
            noexcept(noexcept((*std::forward<Ptr>(ptr)).*pmd))
            -> typename std::enable_if<
                    std::is_object<T>::value &&
                    !is_reference_wrapper<typename std::decay<Ptr>::type>::value &&
                    !std::is_base_of<Base, typename std::decay<Ptr>::type>::value,
                    decltype((*std::forward<Ptr>(ptr)).*pmd)>::type
            {
                return (*std::forward<Ptr>(ptr)).*pmd;
            }

            template<
                typename F,
                typename... Args
            >
            constexpr auto invoke_(
                F && f,
                Args&&... args
            )
            noexcept(noexcept(std::forward<F>( f )( std::forward<Args>( args )...)))
            -> typename std::enable_if<
                    !std::is_member_pointer<typename std::decay<F>::type>::value,
                    decltype(std::forward<F>(f)(std::forward<Args>(args)...))>::type
            {
                return std::forward<F>( f )( std::forward<Args>( args )...);
            }
        } // end namespace detail

    // ----------------------------------------------------------------------------------------

        template< typename F, typename... Args>
        constexpr auto invoke(F && f, Args &&... args)
        /*!
            ensures
                - identical to std::invoke(std::forward<F>(f), std::forward<Args>(args)...)
                - works with C++11 onwards
        !*/
        noexcept(noexcept(detail::invoke_(std::forward<F>( f ), std::forward<Args>( args )...)))
        -> decltype(detail::invoke_(std::forward<F>( f ), std::forward<Args>( args )...))
        {
            return detail::invoke_(std::forward<F>( f ), std::forward<Args>( args )...);
        }

    // ----------------------------------------------------------------------------------------

        namespace detail
        {
            template< typename AlwaysVoid, typename, typename...>
            struct invoke_traits
            {
                static constexpr bool value = false;
            };

            template< typename F, typename... Args >
            struct invoke_traits< decltype( void(dlib::invoke(std::declval<F>(), std::declval<Args>()...)) ), F, Args...>
            {
                static constexpr bool value = true;
                using type = decltype( dlib::invoke(std::declval<F>(), std::declval<Args>()...) );
            };
        } // end namespace detail

    // ----------------------------------------------------------------------------------------

        template< typename F, typename... Args >
        struct invoke_result : detail::invoke_traits< void, F, Args...> {};
        /*!
            ensures
                - identical to std::invoke_result<F, Args..>
                - works with C++11 onwards
        !*/

        template< typename F, typename... Args >
        using invoke_result_t = typename invoke_result<F, Args...>::type;
        /*!
            ensures
                - identical to std::invoke_result_t<F, Args..>
                - works with C++11 onwards
        !*/

    // ----------------------------------------------------------------------------------------

        template< typename F, typename... Args >
        struct is_invocable : std::integral_constant<bool, detail::invoke_traits< void, F, Args...>::value> {};
        /*!
            ensures
                - identical to std::is_invocable<F, Args..>
                - works with C++11 onwards
        !*/

    // ----------------------------------------------------------------------------------------

        template <typename R, typename F, typename... Args>
        struct is_invocable_r : std::integral_constant<bool, dlib::is_invocable<F, Args...>::value &&
                                                             std::is_convertible<invoke_result_t<F, Args...>, R>::value> {};
        /*!
            ensures
                - identical to std::is_invocable_r<R, F, Args..>
                - works with C++11 onwards
        !*/

    // ----------------------------------------------------------------------------------------

        template< typename R, typename F, typename... Args>
        constexpr typename std::enable_if<dlib::is_invocable_r<R, F, Args...>::value, R>::type
        invoke_r(F && f, Args &&... args)
        /*!
            ensures
                - identical to std::invoke_r<R>(std::forward<F>(f), std::forward<Args>(args)...)
                - works with C++11 onwards
        !*/
        noexcept(noexcept(dlib::invoke(std::forward<F>( f ), std::forward<Args>( args )...)))
        {
            return dlib::invoke(std::forward<F>( f ), std::forward<Args>( args )...);
        }

    // ----------------------------------------------------------------------------------------

        namespace detail
        {
            template<typename F, typename Tuple, std::size_t... I>
            constexpr auto apply_impl(F&& fn, Tuple&& tpl, index_sequence<I...>)
            noexcept(noexcept(dlib::invoke(std::forward<F>(fn),
                                           std::get<I>(std::forward<Tuple>(tpl))...)))
            -> decltype(dlib::invoke(std::forward<F>(fn),
                                     std::get<I>(std::forward<Tuple>(tpl))...))
            {
                return dlib::invoke(std::forward<F>(fn),
                                    std::get<I>(std::forward<Tuple>(tpl))...);
            }
        } // end namespace detail

    // ----------------------------------------------------------------------------------------

        template<typename F, typename Tuple>
        constexpr auto apply(F&& fn, Tuple&& tpl)
        /*!
            ensures
                - identical to std::apply(std::forward<F>(f), std::forward<Tuple>(tpl))
                - works with C++11 onwards
        !*/
        noexcept(noexcept(detail::apply_impl(std::forward<F>(fn),
                                             std::forward<Tuple>(tpl),
                                             make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type >::value>{})))
        -> decltype(detail::apply_impl(std::forward<F>(fn),
                                       std::forward<Tuple>(tpl),
                                       make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type >::value>{}))
        {
            return detail::apply_impl(std::forward<F>(fn),
                                      std::forward<Tuple>(tpl),
                                      make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type >::value>{});
        }

    // ----------------------------------------------------------------------------------------

        namespace detail
        {
            template <class T, class Tuple, std::size_t... I>
            constexpr T make_from_tuple_impl( Tuple&& t, index_sequence<I...> )
            {
                return T(std::get<I>(std::forward<Tuple>(t))...);
            }
        } // end namespace detail

    // ----------------------------------------------------------------------------------------

        template <class T, class Tuple>
        constexpr T make_from_tuple( Tuple&& t )
        /*!
            ensures
                - identical to std::make_from_tuple<T>(std::forward<Tuple>(t))
                - works with C++11 onwards
        !*/
        {
            return detail::make_from_tuple_impl<T>(std::forward<Tuple>(t),
                                                   make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type >::value>{});
        }

    // ----------------------------------------------------------------------------------------

    }

    #endif //DLIB_INVOKE_Hh_