/*!
 * \file   /utl/concepts/stl.h
 * \brief  STL's Concepts
 *
 * 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/>.
 *
 */
#ifndef __utl_concepts_stl_h__
#define __utl_concepts_stl_h__

#include <utl/core/impl.h>
#include <utl/meta/meta.h>
#include <utl/utility/invoke.h>

#include <utl/concepts/defines.h>

/*!
 * \brief
 *    STL's core language concepts
 *
 * We provide std concepts in case host's stl does not provide them yet.
 *
 * For more information \see https://en.cppreference.com/w/cpp/concepts
 */
//! @{
namespace utl {

   template <typename T>
   using remove_cvref_t = std::remove_cv_t< std::remove_reference_t<T> >;

   template <typename T>
   using cref_ = const std::remove_reference_t<T>&;

   template <typename T>
   using _ref_t = std::add_lvalue_reference_t<T>;

   template <typename _T1, typename _T2, typename _Ret =_T1>
   using use_if_same_t = meta::eval<
      meta::enable_if<
         meta::same_<_T1, _T2>::value, _Ret
      >
   >;


   /*!
    * Same
    */
   template <class T, class U>
   _utlConcept Same = meta::same_<T, U>::value;

//   template<class T>
//      _utlConcept Decayed = Same<T, std::decay_t<T>>;

   /*!
    * DerivedFrom
    */
   template <class Derived, class Base>
   _utlConcept DerivedFrom =
      std::is_base_of<Base, Derived>::value &&
      std::is_convertible<const volatile Derived*, const volatile Base*>::value;


   /*!
    * ConvertibleTo
    */
   template <class From, class To>
   #if CXX_CONCEPTS
   _utlConcept ConvertibleTo =
      std::is_convertible<From, To>::value &&
      requires(From (&f)()) {
         static_cast<To>(f());
      };
   #else
   _utlConcept ConvertibleTo = std::is_convertible<From, To>::value;
   #endif

   /*!
    * Common Reference
    */
   //! @{
   namespace common_impl {
      //! \see https://ericniebler.github.io/std/wg21/D0022.html
      // ========== common reference ===========
      template<class T, class U>
      using __cond_res =
         decltype(false ? std::declval<T(&)()>()() : std::declval<U(&)()>()());

      template<class From>
      struct __copy_cv_ {
         static_assert(!std::is_reference<From>::value);
         template<class To> using apply = To;
      };
      template<class From>
      struct __copy_cv_<const From> {
         template<class To> using apply = const To;
      };
      template<class From>
      struct __copy_cv_<volatile From> {
         template<class To> using apply = volatile To;
      };
      template<class From>
      struct __copy_cv_<const volatile From> {
         template<class To> using apply = const volatile To;
      };
      template<class From, class To>
      using __copy_cv = meta::invoke<__copy_cv_<From>, To>;

      // CREF [meta.trans.other]/2.1
      template<class T>
      using __cref = std::add_lvalue_reference_t<const std::remove_reference_t<T>>;

      // COMMON_REF [meta.trans.other]/2
      template<class T, class U, class = void>
      struct __common_ref_ {
         static_assert(std::is_reference<T>::value, "");
         static_assert(std::is_reference<U>::value, "");
      };

      template<class T, class U>
      using __common_ref = meta::eval<__common_ref_<T, U>>;

      // [meta.trans.other]/2.5
      template<class T, class U>
      using __lref_res = __cond_res<
         __copy_cv<T, U> &,
         __copy_cv<U, T> &
      >;

      // [meta.trans.other]/2.6
      template<class T, class U, class R = __common_ref<T&, U&>>
      using __rref_res = std::remove_reference_t<R>&&;

      template<class T, class U>
      struct __common_ref_<T&, U&,
         meta::void_t<__lref_res<T, U>,
                      meta::when<std::is_reference<__lref_res<T, U>>::value>> > {
         using type = __lref_res<T, U>;
      };

      template<class T, class U>
      struct __common_ref_<T&&, U&&,
         meta::void_t<__common_ref<T&, U&>,
                      meta::when<ConvertibleTo<T&&, __rref_res<T, U>>>,
                      meta::when<ConvertibleTo<U&&, __rref_res<T, U>>>> > {
         using type = __rref_res<T, U>;
      };

      // [meta.trans.other]/2.7
      template<class T, class U>
      struct __common_ref_<T&&, U&,
         meta::void_t<__common_ref<const T&, U&>,
                      meta::when<ConvertibleTo<T&&, __common_ref<const T&, U&>>>> > {
         using type = __common_ref<const T&, U&>;
      };

      // [meta.trans.other]/2.8
      template<class T, class U>
      struct __common_ref_<T&, U&&,
         meta::void_t<__common_ref<T&, const U&>,
                      meta::when<ConvertibleTo<U&&, __common_ref<T&, const U&>>>> > {
         using type = __common_ref<T&, const U&>;
      };

      template<class>
      struct __xref {
         template<class U> using apply = U;
      };
      template<class T>
      struct __xref<const T> {
         template<class U> using apply = const U;
      };
      template<class T>
      struct __xref<volatile T> {
         template<class U> using apply = volatile U;
      };
      template<class T>
      struct __xref<const volatile T> {
         template<class U> using apply = const volatile U;
      };
      template<class T>
      struct __xref<T&> {
         template<class U> using apply =
            std::add_lvalue_reference_t<meta::invoke<__xref<T>, U>>;
      };
      template<class T>
      struct __xref<T&&> {
         template<class U> using apply =
            std::add_rvalue_reference_t<meta::invoke<__xref<T>, U>>;
      };

      template<class,
               class,
               template<class> class,
               template<class> class
      >
      struct basic_common_reference { };

      template<class T, class U>
      using __basic_common_reference_t = meta::eval<
         basic_common_reference<
            remove_cvref_t<T>,
            remove_cvref_t<U>,
            __xref<T>::template apply,
            __xref<U>::template apply
         >
      >;

      template<class...>
      struct common_reference {};

      template<class... Ts>
      using common_reference_t = meta::eval<
         common_reference<Ts...>
      >;

      // [meta.trans.other]/5.2
      template<class T>
      struct common_reference<T> {
         using type = T;
      };

      // [meta.trans.other]/5.3.4
      template<class T, class U, class...>
      struct __common_reference3
         : std::common_type<T, U> {};

      // [meta.trans.other]/5.3.3
      template<class T, class U>
      struct __common_reference3<T, U,
         meta::void_t<__cond_res<T, U>>> {
         using type = __cond_res<T, U>;
      };

      template<class T, class U, class...>
      struct __common_reference2
         : __common_reference3<T, U> {};

      // [meta.trans.other]/5.3.2
      template<class T, class U>
      struct __common_reference2<T, U,
         meta::void_t<__basic_common_reference_t<T, U>>> {
         using type = __basic_common_reference_t<T, U>;
      };

      template <class T, class U, class...>
      struct __common_reference
         : __common_reference2<T, U> { };

      template <class T, class U>
      struct __common_reference<T, U,
         meta::when<std::is_reference<T>::value && std::is_reference<U>::value>> {
         using type = __common_ref<T, U>;
      };

      template<class T, class U>
      struct common_reference<T, U> : __common_reference<T, U> { };

      // [meta.trans.other]/5.4
      template<class T, class U, class V, class... W>
      //requires requires { typename common_reference_t<T, U>; }
      struct common_reference<T, U, V, W...>
         : common_reference <
            common_reference_t<T, U>, V, W...
         > {};
   }

   template<typename...Ts>
   using common_reference = common_impl::common_reference<Ts...>;

   template<typename... Ts>
   using common_reference_t = meta::eval<
      common_reference<Ts...>
   >;

   //! @}


   //FIXME: CommonReference needs better implementation
   template <class T, class U>
   _utlConcept CommonReference =
      Same<common_reference_t<T, U>, common_reference_t<U, T>> &&
      ConvertibleTo<T, common_reference_t<T, U>> &&
      ConvertibleTo<U, common_reference_t<T, U>>;


   // != std::Common on CommonReference
   template <class T, class U>
   _utlConcept Common =
   #if CXX_CONCEPTS
      Same<std::common_type_t<T, U>, std::common_type_t<U, T>> &&
      requires {
         static_cast<std::common_type_t<T, U>>(std::declval<T>());
         static_cast<std::common_type_t<T, U>>(std::declval<U>());
      };
//      } &&
//      CommonReference<
//         std::add_lvalue_reference_t<const T>,
//         std::add_lvalue_reference_t<const U>> &&
//      CommonReference<
//         std::add_lvalue_reference_t<std::common_type_t<T, U>>,
//         std::common_reference_t<
//            std::add_lvalue_reference_t<const T>,
//            std::add_lvalue_reference_t<const U>
//         >
//      >;
   #else
//      meta::and_ <
         Same<std::common_type_t<T, U>, std::common_type_t<U, T>>; //>
//         meta::bool_<CommonReference<
//               std::add_lvalue_reference_t<const T>,
//               std::add_lvalue_reference_t<const U>
//         >>,
//         meta::bool_< CommonReference<
//               std::add_lvalue_reference_t<std::common_type_t<T, U>>,
//               common_reference_t<
//                  std::add_lvalue_reference_t<const T>,
//                  std::add_lvalue_reference_t<const U>
//               >
//         >>
//      >::value;
   #endif

   /*!
    * Integral
    */
   template <class T>
   _utlConcept Integral = std::is_integral<T>::value;

   /*!
    * Signed Integral
    */
   template <class T>
   _utlConcept SignedIntegral = Integral<T> && std::is_signed<T>::value;

   /*!
    * Unsigned Integral
    */
   template <class T>
   _utlConcept UnsignedIntegral = Integral<T> && !std::is_signed<T>::value;


   template <typename T>
   _utlConcept MoveAssignable = std::is_move_assignable<T>::value;

   template <typename T>
   _utlConcept CopyAssignable = std::is_copy_assignable<T>::value;

   /*!
    * Assignable
    * \note != std:: on CommonReference
    */
   template<class LHS, class RHS>
   _utlConcept Assignable =
   #if CXX_CONCEPTS
      std::is_lvalue_reference<LHS>::value &&
//      CommonReference<
//         const std::remove_reference_t<L>&,
//         const std::remove_reference_t<R>&> &&
      requires(LHS lhs, RHS&& rhs) {
         lhs = std::forward<RHS>(rhs);
         requires Same<
            decltype(lhs = std::forward<RHS>(rhs)), LHS
         >;
      };
   #else
      std::is_assignable<LHS, RHS>::value;
   #endif

   /*!
    * Swappable, SwappableWith
    */
   //! @{
   #if CXX_VER < CXX_VER_STD_17
   namespace swappable_with_impl {
      struct is_swappable_with_ {
         // can apply std::swap
         template<typename _Tp,
                  typename _Up,
                  typename
                     = decltype(std::swap(std::declval<_Tp&>(), std::declval<_Up&>())),
                  typename
                     = decltype(std::swap(std::declval<_Up&>(), std::declval<_Tp&>()))>
         static meta::true_ check(int);
         // can not apply std::swap
         template<typename, typename> static meta::false_ check(...);
      };
   }
   template <typename _Tp, typename _Up>
   struct is_swappable_with
      : swappable_with_impl::is_swappable_with_ {
      using type = decltype(check<_Tp, _Up>(0));
   };
   #else
   using is_swappable = std::is_swappable;
   using is_swappable_with = std::is_swappable_with;
   #endif

   // != std:: on CommonReference
   template<class T, class U>
   _utlConcept SwappableWith =
      is_swappable_with<T, T>::type::value &&
      is_swappable_with<U, U>::type::value &&
      is_swappable_with<T, U>::type::value &&
      is_swappable_with<U, T>::type::value;
//      std::CommonReference<
//         const std::remove_reference_t<T>&,
//         const std::remove_reference_t<U>&
//      >;

   // != std:: we use is_swappable_with now is_swappable
   template<class T>
   _utlConcept Swappable = is_swappable_with<T, T>::type::value;
   //! @}

   /*!
    * Destructible
    */
   template <class T>
   _utlConcept Destructible = std::is_nothrow_destructible<T>::value;

   /*!
    * Constructible
    */
   template <class T, class... Args>
   _utlConcept Constructible =
      Destructible<T> && std::is_constructible<T, Args...>::value;
   /*!
    * DefaultConstructible
    */
   template <class T>
   _utlConcept DefaultConstructible = Constructible<T>;

   /*!
    * MoveConstructible
    * \note
    *    Another approach would be std::is_move_constructible<T>::value;
    */
   template<class T>
   _utlConcept MoveConstructible =
      Constructible<T, T> && ConvertibleTo<T, T>;

   /*!
    * CopyConstructible
    */
   template <class T>
   _utlConcept CopyConstructible =
      MoveConstructible<T> &&
      Constructible<T, _ref_t<T>> && ConvertibleTo<_ref_t<T>, T> &&
      Constructible<T, const _ref_t<T>> && ConvertibleTo<const _ref_t<T>, T> &&
      Constructible<T, const T> && ConvertibleTo<const T, T>;

   /*!
    * Movable
    */
   template <class T>
   _utlConcept Movable =
      std::is_object<T>::value &&
      MoveConstructible<T> &&
      Assignable<_ref_t<T>, T> &&
      Swappable<T>;


   /*!
    * Copyable
    */
   template <class T>
   _utlConcept Copyable =
      CopyConstructible<T> &&
      Movable<T> &&
      Assignable<_ref_t<T>, const _ref_t<T>>;


   /*!
    * Boolean
    */
   #if CXX_CONCEPTS
   template <class B>
   _utlConcept Boolean =
      Movable<remove_cvref_t<B>> &&
      requires(const std::remove_reference_t<B>& b1,
               const std::remove_reference_t<B>& b2, const bool a) {
         requires ConvertibleTo<const std::remove_reference_t<B>&, bool>;
         !b1;      requires ConvertibleTo<decltype(!b1), bool>;
         b1 && a;  requires Same<decltype(b1 && a), bool>;
         b1 || a;  requires Same<decltype(b1 || a), bool>;
         b1 && b2; requires Same<decltype(b1 && b2), bool>;
         a && b2;  requires Same<decltype(a && b2), bool>;
         b1 || b2; requires Same<decltype(b1 || b2), bool>;
         a || b2;  requires Same<decltype(a || b2), bool>;
         b1 == b2; requires ConvertibleTo<decltype(b1 == b2), bool>;
         b1 == a;  requires ConvertibleTo<decltype(b1 == a), bool>;
         a == b2;  requires ConvertibleTo<decltype(a == b2), bool>;
         b1 != b2; requires ConvertibleTo<decltype(b1 != b2), bool>;
         b1 != a;  requires ConvertibleTo<decltype(b1 != a), bool>;
         a != b2;  requires ConvertibleTo<decltype(a != b2), bool>;
      };
   #else
   namespace details {
//      template <typename B> using try_op_not_ = decltype(!std::declval<cref_<B>>());
//      template <typename B> using try_op_eq_  = decltype(std::declval<cref_<B>>() == std::declval<cref_<B>>());
//      template <typename B> using try_op_neq_ = decltype(std::declval<cref_<B>>() != std::declval<cref_<B>>());
//      template <typename B> using try_op_and_ = decltype(std::declval<cref_<B>>() && std::declval<cref_<B>>());
//      template <typename B> using try_op_or_  = decltype(std::declval<cref_<B>>() || std::declval<cref_<B>>());
//
//      template <typename B>
//      struct is_boolean__ {
//         using type = meta::and_ <
//            meta::is_detected<B, try_op_not_>,
//            meta::is_detected<B, try_op_eq_>,
//            meta::is_detected<B, try_op_neq_>,
//            meta::is_detected<B, try_op_and_>,
//            meta::is_detected<B, try_op_or_>
//         >;
//      };

      template <typename B, typename = void>
      struct is_boolean_ {
         using type = meta::false_;
      };

      template <typename B>
      struct is_boolean_ <B, meta::void_t<
            meta::use_if_same_t<bool, decltype(!std::declval<cref_<B>>())>,
            meta::use_if_same_t<bool, decltype(std::declval<cref_<B>>() == std::declval<cref_<B>>())>,
            meta::use_if_same_t<bool, decltype(std::declval<cref_<B>>() != std::declval<cref_<B>>())>,
            meta::use_if_same_t<bool, decltype(std::declval<cref_<B>>() && std::declval<cref_<B>>())>,
            meta::use_if_same_t<bool, decltype(std::declval<cref_<B>>() || std::declval<cref_<B>>())>
      >> {
         using type = meta::true_;
      };

      template <typename B>
      using is_boolean_t = meta::eval <
         is_boolean_<B>
      >;
   }
   template <class B>
   _utlConcept Boolean =
      Movable<remove_cvref_t<B>> &&
      //ConvertibleTo<const std::remove_reference_t<B>&, bool> &&
      ConvertibleTo<const _ref_t<B>, bool> &&
      Same<meta::true_, details::is_boolean_t<B>>;
   #endif


   namespace details {
      template <typename T, typename U, typename = void>
      struct is_weakly_equality_comparable_with_ {
         using type = meta::false_;
      };

      template <typename T, typename U>
      struct is_weakly_equality_comparable_with_<T, U, meta::void_t<
         meta::use_if_same_t<bool, decltype(std::declval<cref_<T>>() == std::declval<cref_<U>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<T>>() != std::declval<cref_<U>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<U>>() == std::declval<cref_<T>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<U>>() != std::declval<cref_<T>>())>
      >> {
         using type = meta::true_;
      };

      template <typename T, typename U>
      using is_weakly_equality_comparable_with_t = meta::eval<
         is_weakly_equality_comparable_with_ <T, U>
      >;
   }

   template <class T, class U>
   _utlConcept WeaklyEqualityComparableWith =
   #if CXX_CONCEPTS
      requires(const std::remove_reference_t<T>& t,
               const std::remove_reference_t<U>& u) {
         t == u; requires Boolean<decltype(t == u)>;
         t != u; requires Boolean<decltype(t != u)>;
         u == t; requires Boolean<decltype(u == t)>;
         u != t; requires Boolean<decltype(u != t)>;
      };
   #else
      Same<meta::true_, details::is_weakly_equality_comparable_with_t<T, U>>;
   #endif

   template <class T>
   _utlConcept EqualityComparable = WeaklyEqualityComparableWith<T, T>;

   template <class T, class U>
   _utlConcept EqualityComparableWith =
      EqualityComparable<T> &&
      EqualityComparable<U> &&
//      CommonReference<
//         const std::remove_reference_t<T>&,
//         const std::remove_reference_t<U>&> &&
//      EqualityComparable<
//         common_reference_t<
//            const std::remove_reference_t<T>&,
//            const std::remove_reference_t<U>&>> &&
      WeaklyEqualityComparableWith<T, U>;



   #if CXX_CONCEPTS
   template <class T>
   _utlConcept StrictTotallyOrdered =
      EqualityComparable<T> &&
      requires(const std::remove_reference_t<T>& a,
               const std::remove_reference_t<T>& b) {
         a < b;  requires Boolean<decltype(a < b)>;
         a > b;  requires Boolean<decltype(a > b)>;
         a <= b; requires Boolean<decltype(a <= b)>;
         a >= b; requires Boolean<decltype(a >= b)>;
     };
   #else
   namespace details {
      template <typename T, typename = void>
      struct is_strict_totally_ordered_ {
         using type = meta::false_;
      };

      template <typename T>
      struct is_strict_totally_ordered_ <T, meta::void_t <
         meta::use_if_same_t<bool, decltype(std::declval<cref_<T>>() < std::declval<cref_<T>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<T>>() > std::declval<cref_<T>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<T>>() <= std::declval<cref_<T>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<T>>() >= std::declval<cref_<T>>())>
      >> {
         using type = meta::true_;
      };

      template <typename T>
      using is_strict_totally_ordered_t = meta::eval <
         is_strict_totally_ordered_<T>
      >;
   }
   template <class T>
   _utlConcept StrictTotallyOrdered =
      EqualityComparable<T> &&
      Same <meta::true_, details::is_strict_totally_ordered_t<T>>;
   #endif

   #if CXX_CONCEPTS
   template <class T, class U>
   _utlConcept StrictTotallyOrderedWith =
      StrictTotallyOrdered<T> &&
      StrictTotallyOrdered<U> &&
//      CommonReference<
//         const std::remove_reference_t<T>&,
//         const std::remove_reference_t<U>&
//      > &&
//      StrictTotallyOrdered<
//         common_reference_t<
//            const std::remove_reference_t<T>&,
//            const std::remove_reference_t<U>&
//         >
//      > &&
      EqualityComparableWith<T, U> &&
      requires(const std::remove_reference_t<T>& t,
               const std::remove_reference_t<U>& u) {
         t < u;  requires Boolean<decltype(t < u)>;
         t > u;  requires Boolean<decltype(t > u)>;
         t <= u; requires Boolean<decltype(t <= u)>;
         t >= u; requires Boolean<decltype(t >= u)>;
         u < t;  requires Boolean<decltype(u < t)>;
         u > t;  requires Boolean<decltype(u > t)>;
         u <= t; requires Boolean<decltype(u <= t)>;
         u >= t; requires Boolean<decltype(u >= t)>;
      };
   #else
   namespace details {
      template <typename T, typename U, typename = void>
      struct is_strict_totally_ordered_with_ {
         using type = meta::false_;
      };

      template <typename T, typename U>
      struct is_strict_totally_ordered_with_ <T, U, meta::void_t <
         meta::use_if_same_t<bool, decltype(std::declval<cref_<T>>() < std::declval<cref_<U>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<T>>() > std::declval<cref_<U>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<T>>() <= std::declval<cref_<U>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<T>>() >= std::declval<cref_<U>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<U>>() < std::declval<cref_<T>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<U>>() > std::declval<cref_<T>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<U>>() <= std::declval<cref_<T>>())>,
         meta::use_if_same_t<bool, decltype(std::declval<cref_<U>>() >= std::declval<cref_<T>>())>
      >> {
         using type = meta::true_;
      };

      template <typename T, typename U>
      using is_strict_totally_ordered_with_t = meta::eval <
         is_strict_totally_ordered_with_<T, U>
      >;
   }
   template <class T, class U>
   _utlConcept StrictTotallyOrderedWith =
      StrictTotallyOrdered<T> &&
      StrictTotallyOrdered<U> &&
      EqualityComparableWith<T, U> &&
      Same <meta::true_, details::is_strict_totally_ordered_with_t<T, U>>;
   #endif

   /*!
    * Semiregular
    */
   template <class T>
   _utlConcept Semiregular = Copyable<T> && DefaultConstructible<T>;

   /*!
    * Regular
    */
   template <class T>
   _utlConcept Regular = Semiregular<T> && EqualityComparable<T>;

   /*!
    * Scalar
    */
   template<class T>
   _utlConcept Scalar =
      std::is_scalar<T>::value && Regular<T>;

   /*!
    * Arithmetic
    */
   template<class T>
   _utlConcept Arithmetic =
      std::is_arithmetic<T>::value && Scalar<T> && StrictTotallyOrdered<T>;

   /*!
    * FloatingPoint
    */
   template<class T>
   _utlConcept FloatingPoint =
      std::is_floating_point<T>::value && Arithmetic<T>;

   /*!
    * Invocable
    */
   template <class F, class... Args>
   _utlConcept Invocable = is_invocable<F, Args...>::value;
//      requires(F&& f, Args&&... args) {
//         invoke(std::forward<F>(f), std::forward<Args>(args)...);
//      };

   template< class F, class... Args >
   _utlConcept RegularInvocable = Invocable<F, Args...>;

   template < class F, class... Args >
   _utlConcept Predicate =
      RegularInvocable<F, Args...> &&
      Boolean<invoke_result_t<F, Args...>>;

   template <class R, class T, class U>
   _utlConcept Relation =
     Predicate<R, T, T> && Predicate<R, U, U> &&
     Predicate<R, T, U> && Predicate<R, U, T>;

   template < class R, class T, class U >
   _utlConcept StrictWeakOrder = Relation<R, T, U>;
}
//! @}




#endif /* __utl_concepts_stl_h__ */
