/*!
 * \file    detection.h
 * \brief   Detection idiom based on WG21's N4502 [\ref 1] from Walter E. Brown
 *
 * Copyright (C) 2018-2019 Christos Choutouridis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * \anchor [1]: www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf
 */
#ifndef __utl_meta_detection_h__
#define __utl_meta_detection_h__

#include <utl/core/impl.h>
#include <utl/meta/operations.h>
#include <type_traits>

/*!
 * \ingroup meta
 * \defgroup detection
 * Detection idiom support header.
 */
//! @{

namespace utl {
namespace meta {

   /*!
    * void_t meta-function that maps a sequence of any types to the type void
    */
   //! @{
   #if defined(UTL_WORKAROUND_CWG_1558)
      template<typename... _Ts>
      struct void_ {
         using type = void;
      };
      //! void_t type alias
      template<typename... _Ts>
      using void_t = eval<void_<_Ts...>>;
   #else
      //! void_ type alias
      template <typename...> using void_  = void;
      //! void_t type alias
      template <typename...> using void_t = void;
   #endif
   //! @}

   /*!
    * Not a type to use in detected idiom. This type can
    * not be constructed, destructed or copied
    */
   struct nat_ {
      nat_()  = delete;
      ~nat_() = delete;
      nat_(nat_ const&)         = delete;
      void operator = (nat_ const&) = delete;
   };

   //! Detector for detection idiom
   //! @{
   namespace detail {
      template <typename Default,
                typename AlwaysVoid,
                template<typename...> class Op, typename... Args>
      struct detector {
         using detected = false_;
         using type = Default;
      };

      template <typename Default,
                template<typename...> class Op, typename... Args>
      struct detector <Default, void_t<Op<Args...>>, Op, Args...> {
         using detected = true_;
         using type = Op<Args...>;
      };

      //! helper for detected_or_t
      template <typename Default,
                template<typename...> class Op, typename... Args>
      using detected_or = detector<Default, void, Op, Args...>;
   } // namespace detail
   //! @}

   /*!
    * detection interface
    */
   //! @{

   /*!
    *  Checks if Op<Args...> is a valid expression without evaluating it.
    *
    *  \tparam  Op       a meta-callback function to pass Args...
    *  \tparam  Args...  types to pass to Op for checking
    *  \return status of the operation [bool_]
    *    \arg  true_    if Op<Args...> is valid expression
    *    \arg  false_   if Op<Args...> is not valid
    *
    * \example
    * \code
    *    // archetypal alias for a copy assignment operation
    *    template< class T > using copy_assign_t = decltype( declval<T&>() = declval<T const &>() );
    *
    *    template< class T > using is_copy_assignable = is_detected< copy_assign_t, T >;
    * \endcode
    */
   template <template<typename...> class Op, typename... Args>
   using is_detected = typename detail::detector<nat_, void, Op, Args...>::detected;

   //! Detection predicate
   template< template<typename...> class Op, typename... Args>
   constexpr bool is_detected_v = is_detected<Op, Args...>::value;

   /*!
    * Detection tool that evaluates to Op<Args...> if it's valid and to nat_ if not
    *
    * \param   Op       metafunction detector
    * \param   Args...  The arguments to pass to \p Op and check if is well formed
    * \return  The result type
    *    \arg  Op<Args...>    if is well formed
    *    \arg  nat_           if Op<Args...> is ill formed
    * \example
    * \code
    *    template <typename T> using try_type = typename T::type;                   // detector
    *    template <typename T> using try_ppT  = decltype (++(std::declval<T>()));   // detector
    *    static_assert( std::is_same<nat_, detected_t<try_type, A<int>> >(), "");   // detection failed
    *    static_assert( std::is_same<A<int>&, detected_t<try_ppT, A<int>> >(), ""); // detection succeed
    *
    *    // if mFun<int, int> is well formed
    *    static_assert(std::is_same< mFun<int, int>, detected_t<mFun, int, int> >(), "");
    * \endcode
    */
   template <template<typename...> class Op, typename... Args>
   using detected_t = eval <
      detail::detector<nat_, void, Op, Args...>
   >;

   /*!
    * Detection tool that evaluates to Op<Args...> if it's valid and to \p Default if not
    *
    * \param   Default  The resulting type if detection fail
    * \param   Op       metafunction detector
    * \param   Args...  The arguments to pass to \p Op and check if is well formed
    * \return  The result type
    *    \arg  Op<Args...>    if is well formed
    *    \arg  Default        if Op<Args...> is ill formed
    * \example
    * \code
    *    template <typename T> using try_type = typename T::type;                   // detector
    *    template <typename T> using try_ppT  = decltype (++(std::declval<T>()));   // detector
    *    static_assert( std::is_same<Foo, detected_or_t<Foo, try_type, A<int>> >(), "");     // detection failed
    *    static_assert( std::is_same<A<int>&, detected_or_t<Foo, try_ppT, A<int>> >(), "");  // detection succeed
    *
    *    // if mFun<int, int> is well formed
    *    static_assert(std::is_same< mFun<int, int>, detected_or_t<void, mFun, int, int> >(), "");
    * \endcode
    */
   template <typename Default,
             template<typename...> class Op, typename... Args>
   using detected_or_t = eval <
      detail::detected_or<Default, Op, Args...>
   >;

   /*!
    * Detection tool that evaluates to true_ if evaluation of Op<Args...>
    * is \p Expected and to false_ if not
    *
    * \param   Expected The expected resulting type if detection succeed
    * \param   Op       metafunction detector
    * \param   Args...  The arguments to pass to \p Op and check if is well formed
    * \return  The result type
    *    \arg  true_    if Op<Args...> is well formed and evaluate to Expected
    *    \arg  false_   Any other case
    * \example
    * \code
    *    template <typename T> using try_type = typename T::type;                   // detector
    *    template <typename T> using try_ppT  = decltype (++(std::declval<T>()));   // detector
    *    static_assert( std::is_same<false_, is_detected_exact<int, try_type, A<int>> >(), "");    // detection failed
    *    static_assert( std::is_same<true_,  is_detected_exact<A<int>&, try_ppT, A<int>> >(), ""); // detection succeed
    *
    *    // if mFun<int, int> is well formed
    *    static_assert(std::is_same< true_, is_detected_exact<mFun<int, int>, mFun, int, int> >(), "");
    * \endcode
    */
   template <typename Expected,
             template<typename...> class Op, typename... Args >
   using is_detected_exact = eval <
      same_<Expected, detected_t<Op, Args...>>
   >;

   //! evaluates to true if evaluation of Op<Args...> is \p Expected and to false if not
   template <typename Expected,
             template<typename...> class Op, typename... Args >
   constexpr bool is_detected_exact_v = is_detected_exact< Expected, Op, Args...>::value;

   /*!
    * Detection tool that evaluates to true_ if evaluation of Op<Args...> is convertible
    * to \p To and to false_ if not
    *
    * \param   To       The to convert to if detection succeed
    * \param   Op       metafunction detector
    * \param   Args...  The arguments to pass to \p Op and check if is well formed
    * \return  The result type
    *    \arg  true_    if Op<Args...> is well formed and convertible to To
    *    \arg  false_   Any other case
    * \example
    * \code
    *    template <typename T> using try_type = typename T::type;                   // detector
    *    template <typename T> using try_ppT  = decltype (++(std::declval<T>()));   // detector
    *    static_assert( std::is_same<false_, is_detected_convertible<Foo, try_type, A<int>> >(), "");    // detection failed
    *    static_assert( std::is_same<true_,  is_detected_convertible<A<int>&&, try_ppT, A<int>> >(), "");// detection succeed
    *
    *    // if mFun<int, int> is well formed but not convertible to Foo
    *    static_assert(std::is_same< false_, is_detected_convertible<Foo, mFun, int, int> >(), "");
    * \endcode
    */
   template <typename To,
             template<typename...> class Op, typename... Args >
   using is_detected_convertible = eval <
      std::is_convertible< detected_t<Op, Args...>, To >
   >;

   //! evaluates to true if evaluation of Op<Args...> is convertible to \p To
   //! and to false if not
   template <typename To,
             template<typename...> class Op, typename... Args >
   constexpr bool is_detected_convertible_v =
      is_detected_convertible<To, Op, Args...>::value;
   //! @}

}}
//!@}

#endif /* __utl_meta_detection_h__ */
