如何修复 SFINAE 检查 operator== 是否存在,以便它与 std::pair 一起使用

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

我有一个简单的 SFINAE 检查器,用于检查给定类型 (T) 是否定义了 operator==

这通常效果很好,但不适用于 std::pair<>

我想我明白为什么它不起作用 - std::pair<> 似乎无条件地提供 operator== 支持,即使它的一对不支持。

我正在就如何更正我的 SFINAE operator== 检查器征求意见,以便它与 pair<>

一起使用

您可以在 https://onlinegdb.com/rmAQS7V091

在线测试此代码

但为了完整起见,它也直接包含在这里:

#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;
}

这是我天真的修复:在 #define

中添加两行代码
  template <typename X>   \
            static substitution_failure check (std::pair<X,X>); \

一共:

#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;


正如 kenash0625 正确指出的那样,您需要 std::pair 的专门化来手动实施 std::pair 本身不执行的检查。但是,在这一点上,我建议使用 C++17 且不使用宏来简化您的实现:

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;

我们现在可以像这样实现 has_eq

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>>);

我们也可以写一个helper,其首要目的是增加代码的表现力:

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

static_assert( ! has_eq_v<SimpleClassWithoutComparisonOperators>);

虽然最初的问题还没有解决,但这个助手的第二个目的是我们可以为 std::pair 执行一些临时检查。让我们添加专业:

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

在这里我们可以为此类特定情况添加越来越多的专业化:

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_>);

进一步阅读:

要修复 std::pair 案例,您可以针对它专门化您的特征:

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

Demo