【问题标题】:how to fix SFINAE check for operator== existing, so that it works with std::pair如何修复 SFINAE 检查 operator== 存在,以便它与 std::pair 一起使用
【发布时间】:2022-01-04 04:52:00
【问题描述】:

我有一个简单的 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;
}

【问题讨论】:

    标签: c++ templates c++17 sfinae


    【解决方案1】:

    要修复 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

    【讨论】:

      【解决方案2】:

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

      进一步阅读:

      【讨论】:

      • 这是一个很好的答案!谢谢!我特别喜欢你超越并帮助我用你的 is_detected_v 方法替换旧的宏东西!以及您关于如何改进这一点和概念的链接。我还没有机会阅读所有这些内容,但很期待。
      • 一个轻微的检测到这个答案(到目前为止所有的答案) - 是equal_to。它与 pair 有相同的问题 - 即使下属 == 不存在,也定义 operator() 。使用相同的可能解决方法 - 部分模板专业化。但是 - 如果有人没有为 T 定义 operator==,但他们自己指定了 equal_to 怎么办?这似乎是 C++ 允许的,然后这个检查代码不起作用(忽略特化)。
      • 标准库中的现有做法(用于标准库模板专业化)是将此类行为视为完全未定义。这从标准中删除了库对此类代码引起的行为的任何责任。在许多其他图书馆中,这就像用户和图书馆之间的(非)书面合同——你(作为图书馆用户)不应该做这样的坏事
      【解决方案3】:

      这是我的幼稚修复:在#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;
      
      
      

      【讨论】:

      • 这将无条件拒绝pair,而OP只想在first_type或second_type不可比较时拒绝pair。
      • @Jarod42 谢谢,你的回答比我清楚,我学到了很多
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-16
      • 2011-05-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多