Как проверить, существует ли оператор ==?

Я пытаюсь создать пример, который бы проверял существование operator== (функция-член или не-член). Проверить, является ли класс членом operator==, легко, но как проверить, имеет ли он нечлен operator==?

Это то, что мне нужно:

#include <iostream>

struct A
{
    int  a;

    #if 0
    bool operator==( const A& rhs ) const
    {
        return ( a==rhs.a);
    }
    #endif
};
#if 1
bool operator==( const A &l,const A &r )
{
    return ( l.a==r.a);
}
#endif


template < typename T >
struct opEqualExists
{
    struct yes{ char a[1]; };
    struct no { char a[2]; };

    template <typename C> static yes test( typeof(&C::operator==) );
    //template <typename C> static yes test( ???? );
    template <typename C> static no test(...);

    enum { value = (sizeof(test<T>(0)) == sizeof(yes)) };
};

int main()
{
    std::cout<<(int)opEqualExists<A>::value<<std::endl;
}

Можно ли написать тестовую функцию для проверки существования нечлена operator==? Если да, то как?

btw Я проверил похожие вопросы, но не нашел правильного решения:
Можно ли использовать SFINAE/templates для проверки наличия оператора?

Это то, что я пробовал:

template <typename C> static yes test( const C*,bool(*)(const C&,constC&) = &operator== );

но компиляция завершается с ошибкой, если удаляется оператор-член ==

Ответ 1

С++ 03

Следующий трюк работает. И он может использоваться для всех таких операторов:

namespace CHECK
{
  class No { bool b[2]; };
  template<typename T, typename Arg> No operator== (const T&, const Arg&);

  bool Check (...);
  No& Check (const No&);

  template <typename T, typename Arg = T>
  struct EqualExists
  {
    enum { value = (sizeof(Check(*(T*)(0) == *(Arg*)(0))) != sizeof(No)) };
  };  
}

Использование:

CHECK::EqualExists<A>::value;

Второй template typename Arg полезен для некоторых особых случаев, таких как A::operator==(short), где он не похож на class. В таких случаях использование:

CHECK::EqualExists<A, short>::value
//                    ^^^^^ argument of `operator==`

Демо.


С++ 11

Нам не нужно использовать трюк sizeof, когда мы имеем decltype

namespace CHECK
{
  struct No {}; 
  template<typename T, typename Arg> No operator== (const T&, const Arg&);

  template<typename T, typename Arg = T>
  struct EqualExists
  {
    enum { value = !std::is_same<decltype(*(T*)(0) == *(Arg*)(0)), No>::value };
  };  
}

Демо

Ответ 2

Посмотрите библиотеку проверки возможностей Boost (BCCL) http://www.boost.org/doc/libs/1_46_1/libs/concept_check/concept_check.htm.

Он позволяет вам писать требования, которые должен соответствовать классу для компиляции программы. Вы относительно свободны с тем, что можете проверить. Например, проверка наличия operator== класса Foo будет записываться следующим образом:

#include <boost/concept_check.hpp>


template <class T>
struct opEqualExists;

class Foo {
public:
    bool operator==(const Foo& f) {
       return true;
    }

   bool operator!=(const Foo& f) {
      return !(*this == f);
   }

   // friend bool operator==(const Foo&, const Foo&);
   // friend bool operator!=(const Foo&, const Foo&);
};

template <class T>
struct opEqualExists {
   T a;
   T b;

   // concept requirements  
   BOOST_CONCEPT_USAGE(opEqualExists) {
      a == b;
   }
};


/*
bool operator==(const Foo& a, const Foo& b) {
   return true; // or whatever
}
*/


/*
bool operator!=(const Foo& a, const Foo& b) {
   return ! (a == b); // or whatever
}
*/


int main() {
   // no need to declare foo for interface to be checked

   // declare that class Foo models the opEqualExists concept
   //   BOOST_CONCEPT_ASSERT((opEqualExists<Foo>));
   BOOST_CONCEPT_ASSERT((boost::EqualityComparable<Foo>)); // need operator!= too
}

Этот код компилируется до тех пор, пока доступна одна из двух реализаций operator==.

Следуя советам @Matthieu M. и @Luc Touraille, я обновил фрагмент кода, чтобы предоставить пример использования boost::EqualityComparable. Еще раз обратите внимание, что EqualityComparable заставляет вас также объявлять operator!=.

Ответ 3

Также возможно использовать только признаки типа С++ 11 для проверки существования члена:

#include <type_traits>
#include <utility>

template<class T, class EqualTo>
struct has_operator_equal_impl
{
    template<class U, class V>
    static auto test(U*) -> decltype(std::declval<U>() == std::declval<V>());
    template<typename, typename>
    static auto test(...) -> std::false_type;

    using type = typename std::is_same<bool, decltype(test<T, EqualTo>(0))>::type;
};

template<class T, class EqualTo = T>
struct has_operator_equal : has_operator_equal_impl<T, EqualTo>::type {};

Вы можете использовать эту черту так:

bool test = has_operator_equal<MyClass>::value;

Результирующий тип has_operator_equal будет либо std::true_type, либо std::false_type (поскольку он наследует от псевдонима std::is_same::type), и оба определяют статический член value, который является логическим.


Если вы хотите проверить, определяет ли ваш класс operator==(someOtherType), вы можете установить второй аргумент шаблона:

bool test = has_operator_equal<MyClass, long>::value;

где параметр шаблона MyClass по-прежнему является классом, который вы тестируете для наличия operator==, а long - это тип, с которым вы хотите сравнить, например. чтобы проверить, что MyClass имеет operator==(long).

если EqualTo (как в первом примере) оставлено неуказанным, по умолчанию будет T, результатом будет нормальное определение operator==(MyClass).

Обратите внимание: этот признак в случае operator==(long) будет истинным для long, или любое значение, неявно конвертируемое в long, например. double, int и т.д.


Вы также можете определить проверки для других операторов и функций, просто заменив то, что находится внутри decltype. Чтобы проверить !=, просто замените

static auto test(U*) -> decltype(std::declval<U>() == std::declval<V>());

с

static auto test(U*) -> decltype(std::declval<U>() != std::declval<V>());

Ответ 4

Я знаю, что этот вопрос уже давно был дан ответ, но я подумал, что может быть интересно, если кто-то найдет этот вопрос в будущем, что Boost просто добавил кучу признаков "имеет оператор" в свою библиотеку type_traits, и среди них есть has_equal_to, что делает то, о чем просил OP.

Ответ 5

Как и в случае С++ 14, стандартные функции для большинства операций выполняются для большинства операторов.

#include <utility>
#include <iostream>
#include <string>
#include <algorithm>
#include <cassert>


template<class X, class Y, class Op>
struct op_valid_impl
{
    template<class U, class L, class R>
    static auto test(int) -> decltype(std::declval<U>()(std::declval<L>(), std::declval<R>()),
                                      void(), std::true_type());

    template<class U, class L, class R>
    static auto test(...) -> std::false_type;

    using type = decltype(test<Op, X, Y>(0));

};

template<class X, class Y, class Op> using op_valid = typename op_valid_impl<X, Y, Op>::type;

namespace notstd {

    struct left_shift {

        template <class L, class R>
        constexpr auto operator()(L&& l, R&& r) const
        noexcept(noexcept(std::forward<L>(l) << std::forward<R>(r)))
        -> decltype(std::forward<L>(l) << std::forward<R>(r))
        {
            return std::forward<L>(l) << std::forward<R>(r);
        }
    };

    struct right_shift {

        template <class L, class R>
        constexpr auto operator()(L&& l, R&& r) const
        noexcept(noexcept(std::forward<L>(l) >> std::forward<R>(r)))
        -> decltype(std::forward<L>(l) >> std::forward<R>(r))
        {
            return std::forward<L>(l) >> std::forward<R>(r);
        }
    };

}

template<class X, class Y> using has_equality = op_valid<X, Y, std::equal_to<>>;
template<class X, class Y> using has_inequality = op_valid<X, Y, std::not_equal_to<>>;
template<class X, class Y> using has_less_than = op_valid<X, Y, std::less<>>;
template<class X, class Y> using has_less_equal = op_valid<X, Y, std::less_equal<>>;
template<class X, class Y> using has_greater_than = op_valid<X, Y, std::greater<>>;
template<class X, class Y> using has_greater_equal = op_valid<X, Y, std::greater_equal<>>;
template<class X, class Y> using has_bit_xor = op_valid<X, Y, std::bit_xor<>>;
template<class X, class Y> using has_bit_or = op_valid<X, Y, std::bit_or<>>;
template<class X, class Y> using has_left_shift = op_valid<X, Y, notstd::left_shift>;
template<class X, class Y> using has_right_shift = op_valid<X, Y, notstd::right_shift>;

int main()
{
    assert(( has_equality<int, int>() ));
    assert((not has_equality<std::string&, int const&>()()));
    assert((has_equality<std::string&, std::string const&>()()));
    assert(( has_inequality<int, int>() ));
    assert(( has_less_than<int, int>() ));
    assert(( has_greater_than<int, int>() ));
    assert(( has_left_shift<std::ostream&, int>() ));
    assert(( has_left_shift<std::ostream&, int&>() ));
    assert(( has_left_shift<std::ostream&, int const&>() ));

    assert((not has_right_shift<std::istream&, int>()()));
    assert((has_right_shift<std::istream&, int&>()()));
    assert((not has_right_shift<std::istream&, int const&>()()));
}

Ответ 6

IMO, это должно быть частью самого класса, поскольку оно касается частных атрибутов класса. Шаблоны интерпретируются во время компиляции. По умолчанию он генерирует конструкторы operator==, конструктор, деструктор и копию, которые выполняют побитную копию (мелкая копия) или побитовые сравнения для объекта того же типа. Особые случаи (разные типы) должны быть перегружены. Если вы используете глобальную функцию оператора, вам нужно будет объявить функцию как друга для доступа к частной части, иначе вы должны предоставить необходимые интерфейсы. Иногда это действительно уродливо, что может вызвать ненужную функцию функции.

Ответ 7

Просто для справки, я публикую, как я решил свою проблему, без необходимости проверять, существует ли operator==:

#include <iostream>
#include <cstring>

struct A
{
    int  a;
    char b;

    #if 0
    bool operator==( const A& r ) const
    {
        std::cout<<"calling member function"<<std::endl;

        return ( ( a==r.a ) && ( b==r.b ) );
    }
    #endif
};
#if 1
bool operator==( const A &l,const A &r )
{
    std::cout<<"calling NON-member function"<<std::endl;
    return ( ( l.a==r.a ) &&( l.b==r.b ) );
}
#endif

namespace details
{
struct anyType
{
    template < class S >
    anyType( const S &s ) :
        p(&s),
        sz(sizeof(s))
    {
    }

    const void *p;
    int sz;
};
bool operator==( const anyType &l, const anyType &r )
{
    std::cout<<"anyType::operator=="<<std::endl;
    return ( 0 == std::memcmp( l.p, r.p, l.sz ) );
}
} // namespace details

int main()
{
    A a1;
    a1.a=3;a1.b=0x12;
    A a2;
    a2.a=3;a2.b=0x12;

    using details::operator==;

    std::cout<< std::boolalpha << "numbers are equals : " << ( a1 == a2 ) <<std::endl;
}

Ответ 8

Рассмотрим мета-функцию следующего вида, которая проверяет существование оператора равенства (т.е. ==) для данного типа:

template<typename T>
struct equality { .... };

Однако это может быть недостаточно для некоторых угловых случаев. Например, скажем, ваш класс X определяет operator==, но он не возвращает bool, вместо этого он возвращает Y. Итак, в этом случае, что должно equality<X>::value вернуться? true или false? Ну, это зависит от конкретного варианта использования, который мы не знаем сейчас, и, похоже, не рекомендуется принимать что-либо и навязывать его пользователям. Однако в целом мы можем предположить, что тип возврата должен быть bool, поэтому давайте выразить это в самом интерфейсе:

template<typename T, typename R = bool>
struct equality { .... };

Значение по умолчанию для R равно bool, что указывает на общий случай. В случаях, когда тип возврата operator== отличается, например Y, вы можете сказать следующее:

equality<X, Y>  //return type = Y

который также проверяет заданный тип возвращаемого значения. По умолчанию

equality<X>   //return type = bool

Вот одна реализация этой мета-функции:

namespace details
{
    template <typename T, typename R, typename = R>
    struct equality : std::false_type {};

    template <typename T, typename R>
    struct equality<T,R,decltype(std::declval<T>()==std::declval<T>())> 
       : std::true_type {};
}

template<typename T, typename R = bool>
struct equality : details::equality<T, R> {};

Тест:

struct A  {};
struct B  {  bool operator == (B const &); };
struct C  {  short operator == (C const &); };

int main()
{
    std::cout<< "equality<A>::value = " << equality<A>::value << std::endl;
    std::cout<< "equality<B>::value = " << equality<B>::value << std::endl;
    std::cout<< "equality<C>::value = " << equality<C>::value << std::endl;
    std::cout<< "equality<B,short>::value = " << equality<B,short>::value << std::endl;
    std::cout<< "equality<C,short>::value = " << equality<C,short>::value << std::endl;
}

Вывод:

equality<A>::value = 0
equality<B>::value = 1
equality<C>::value = 0
equality<B,short>::value = 0
equality<C,short>::value = 1

Онлайн-демонстрация

Надеюсь, что это поможет.