Как проверить, существует ли оператор ==? - PullRequest
45 голосов
/ 30 июня 2011

Я пытаюсь создать пример, который бы проверял существование 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==? Если да, то как?

Кстати, я проверил похожие вопросы, но не нашел правильного решения:
Можно ли использовать SFINAE / шаблоны для проверки существования оператора?

Вот что я попробовал:

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

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

Ответы [ 10 ]

37 голосов
/ 30 июня 2011

C ++ 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==`

Демо .


C ++ 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 };
  };  
}

Демо

16 голосов
/ 30 июня 2011

Взгляните на Библиотека проверки концепции 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!=.

10 голосов
/ 04 февраля 2016

Также можно использовать только черты типа c ++ 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>());
3 голосов
/ 06 сентября 2016

Начиная с c ++ 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&>()()));
}
2 голосов
/ 31 мая 2018

На этот вопрос уже отвечали несколько раз, но есть более простой способ проверить наличие operator== или, в принципе, любую другую операцию (например, тестирование функции-члена с определенным именем), используя decltype вместе с оператором ,:

namespace detail
{
    template<typename L, typename R>
    struct has_operator_equals_impl
    {
        template<typename T = L, typename U = R> // template parameters here to enable SFINAE
        static auto test(T &&t, U &&u) -> decltype(t == u, void(), std::true_type{});
        static auto test(...) -> std::false_type;
        using type = decltype(test(std::declval<L>(), std::declval<R>()));
    };
} // namespace detail

template<typename L, typename R = L>
struct has_operator_equals : detail::has_operator_equals_impl<L, R>::type {};

Этот же подход можно использовать для проверки, имеет ли тип T функцию-член foo, которая вызывается с определенным списком аргументов:

namespace detail
{
    template<typename T, typename ...Args>
    struct has_member_foo_impl
    {
        template<typename T_ = T>
        static auto test(T_ &&t, Args &&...args) -> decltype(t.foo(std::forward<Args>(args)...), void(), std::true_type{});
        static auto test(...) -> std::false_type;
        using type = decltype(test(std::declval<T>(), std::declval<Args>()...));
    };
} // namespace detail

template<typename T, typename ...Args>
struct has_member_foo : detail::has_member_foo_impl<T, Args...>::type {};

Я думаю, это делает код более понятным.Кроме того, это решение C ++ 11, поэтому оно не зависит от каких-либо новых функций C ++ 14 или C ++ 17.Конечный результат, конечно, тот же, но это стало моей любимой идиомой для тестирования подобных вещей.

Редактировать: Исправлен безумный случай перегруженного оператора запятой, я всегдапропустить это.

1 голос
/ 27 февраля 2018

c ++ 17 слегка модифицированная версия Ричарда Ходжеса Godbolt

#include <functional>
#include <type_traits>

template<class T, class R, class ... Args>
std::is_convertible<std::invoke_result_t<T, Args...>, R> is_invokable_test(int);

template<class T, class R, class ... Args>
std::false_type is_invokable_test(...);

template<class T, class R, class ... Args>
using is_invokable = decltype(is_invokable_test<T, R, Args...>(0));

template<class T, class R, class ... Args>
constexpr auto is_invokable_v = is_invokable<T, R, Args...>::value;

template<class L, class R = L>
using has_equality = is_invokable<std::equal_to<>, bool, L, R>;
template<class L, class R = L>
constexpr auto has_equality_v = has_equality<L, R>::value;

struct L{};

int operator ==(int, L&&);

static_assert(has_equality_v<int>);
static_assert(!has_equality_v<L>);
static_assert(!has_equality_v<L, int>);
static_assert(has_equality_v<int, L>);
1 голос
/ 12 января 2012

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

0 голосов
/ 01 апреля 2016

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

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

Демоверсия

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

0 голосов
/ 01 июля 2011

Просто для справки я выкладываю, как решил свою проблему, без необходимости проверять, существует ли 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;
}
0 голосов
/ 30 июня 2011

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

...