Home > Enterprise >  how to fix SFINAE check for operator== existing, so that it works with std::pair
how to fix SFINAE check for operator== existing, so that it works with std::pair

Time:11-26

I have a simple SFINAE checker that checks a given type (T) if it has an operator== defined for it.

This generally works fairly well, but it does not work for std::pair<>.

I think I understand why it doesn't work - std::pair<> appears to unconditionally provide operator== support, even if the underlying types its a pair of do not.

I'm soliciting views on how to correct my SFINAE operator== checker, so that it works with pair<>

You can test this code online at https://onlinegdb.com/rmAQS7V091

But for completeness sake, its included here directly as well:

#include <iostream>
#include <functional>       // needed for std::equal_to
#include <iterator>

using namespace std;

namespace
{

  using namespace std;

    /**
     *  LIFTED -@todo add docs/reference
     *  from Stroustrup C  11 book - page 800
     */
  struct substitution_failure
  {
  };

    /**
     *  LIFTED -@todo add docs/reference
     *  from Stroustrup C  11 book - page 800
     */
  template < typename T > struct substitution_succeeded:true_type
  {
  };
  template <> struct substitution_succeeded <substitution_failure >:false_type
  {
  };

#define STROIKA_FOUNDATION_CONFIGURATION_DEFINE_HAS(NAME, XTEST)                                                                              \
    namespace Private_ {                                                                                                                          \
        template <typename T>                                                                                                                     \
        struct NAME##_result_impl {                                                                                                               \
            template <typename X>                                                                                                                 \
            static auto                                                     check (const X& x) -> decltype (XTEST);                               \
            static substitution_failure check (...);                                                          \
            using type = decltype (check (declval<T> ()));                                                                                        \
        };                                                                                                                                        \
    }                                                                                                                                             \
    template <typename T>                                                                                                                         \
    using NAME##_result = typename Private_::NAME##_result_impl<T>::type;                                                                         \
    template <typename T>                                                                                                                         \
    struct has_##NAME : integral_constant<bool, not is_same<NAME##_result<T>, substitution_failure>::value> { \
    };                                                                                                                                            \
    template <typename ITERABLE>                                                                                                                  \
    constexpr bool Has##NAME##_v = has_##NAME<ITERABLE>::value;


  STROIKA_FOUNDATION_CONFIGURATION_DEFINE_HAS (eq, (x == x));   // SEE https://stroika.atlassian.net/browse/STK-749
  STROIKA_FOUNDATION_CONFIGURATION_DEFINE_HAS (equal_to, (static_cast<bool> (std::equal_to<X>{}(x, x))));


  class SimpleClassWithoutComparisonOperators
  {
  public:
    SimpleClassWithoutComparisonOperators (size_t v);
    SimpleClassWithoutComparisonOperators (const
                       SimpleClassWithoutComparisonOperators
                       & f);
     ~SimpleClassWithoutComparisonOperators ();

    size_t GetValue () const;
    static size_t GetTotalLiveCount ();

    //explicit operator size_t () const { return fValue; }

    SimpleClassWithoutComparisonOperators operator  (const
                             SimpleClassWithoutComparisonOperators
                             & rhs) const
    {
      return SimpleClassWithoutComparisonOperators (fValue   rhs.fValue);
    }

  private:
      size_t fValue;
    int fConstructed;
    static size_t sTotalLiveObjects;
  };


  static_assert (!has_eq < SimpleClassWithoutComparisonOperators >::value);

  using PAIR_ =
    std::pair < SimpleClassWithoutComparisonOperators,
    SimpleClassWithoutComparisonOperators >;
  static_assert (!has_eq < PAIR_ >::value);     /// THIS IS WHAT FAILS


}

int
main ()
{
  cout << "Hello World";

  return 0;
}

CodePudding user response:

To fix std::pair case, you might specialize your traits for it:

template <typename T1, typename T2>
struct has_eq<std::pair<T1, T2>> :
    integral_constant<bool, has_eq<T1>::value && has_eq<T1>::value>
{};

Demo

CodePudding user response:

As kenash0625 correctly noted, you need a specialization for std::pair to manually implement checks that std::pair itself does not do. However, at this point, I suggest simplifying your implementation with C 17 and without macros:

template<
    template<typename...> typename Detector, 
    typename T, 
    typename SFINAE = void>
constexpr inline bool is_detected_v = false; 

template<
    template<typename...> typename Detector, 
    typename T>
constexpr inline bool is_detected_v<
    Detector, T, std::void_t<Detector<T>>> = true;

We can now implement has_eq like this:

template<typename T>
using has_eq_t = decltype(std::declval<T>() == std::declval<T>());

static_assert(is_detected_v<has_eq_t, std::pair<int, int>>);

We can also write a helper, whose first purpose is to increase code expressiveness:

template<typename T>
constexpr inline bool has_eq_v = is_detected_v<has_eq_t, T>;

static_assert( ! has_eq_v<SimpleClassWithoutComparisonOperators>);

Although the original issue has not been fixed yet, the second purpose of this helper is where we can implement some ad hoc checks for std::pair. Let's add the specialization:

template<typename T, typename U>
constexpr inline bool has_eq_v<std::pair<T, U>> = has_eq_v<T> && has_eq_v<U>;

Here we can add more and more specializations for such specific cases:

template<typename... Ts>
constexpr inline bool has_eq_v<std::tuple<Ts...>> = (has_eq_v<Ts> && ...); 

Check the code now:

static_assert ( ! has_eq_v<PAIR_>);

Further reading:

CodePudding user response:

Here is my naive fix: add two lines of code to #define,

  template <typename X>   \
            static substitution_failure check (std::pair<X,X>); \

altogether:

#define STROIKA_FOUNDATION_CONFIGURATION_DEFINE_HAS(NAME, XTEST)                                                                              \
    namespace Private_ {                                                                                                                          \
        template <typename T>                                                                                                                     \
        struct NAME##_result_impl {                                                                                                               \
            template <typename X>                                                                                                                 \
            static auto                                                     check (const X& x) -> decltype (XTEST);                               \
            static substitution_failure check (...);                                                          \
            template <typename X>   \
            static substitution_failure check (std::pair<X,X>); \
            using type = decltype (check (declval<T> ()));                                                                                        \
        };                                                                                                                                        \
    }                                                                                                                                             \
    template <typename T>                                                                                                                         \
    using NAME##_result = typename Private_::NAME##_result_impl<T>::type;                                                                         \
    template <typename T>                                                                                                                         \
    struct has_##NAME : integral_constant<bool, not is_same<NAME##_result<T>, substitution_failure>::value> { \
    };                                                                                                                                            \
    template <typename ITERABLE>                                                                                                                  \
    constexpr bool Has##NAME##_v = has_##NAME<ITERABLE>::value;


  • Related