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

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

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

14 ответов

Решение

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;

2-й 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 };
  };  
}

демонстрация

Взгляните на библиотеку Boost's Concept Check Library (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!= тоже.

Также можно использовать только черты типа 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>());

C++20

Я предполагаю, что вы хотите проверить, есть ли у пользователя тип шаблона оператора равенства или нет, если это так, здесь вам помогут концепции.

#include<concepts>

struct S{
   int x;
};

template<class T>
requires std::EqualityComparable<T>
void do_magic(T a, T b){
    return a == b;
}

int main(){
    // do_magic(S{}, S{}); Compile time error
    do_magic(56, 46); // Okay int has == and !=

return 0;
}

Если вы передаете любой тип, который не имеет == а также != перегрузить компилятор только с ошибками с сообщением:

EqualityComparable концепция не удовлетворена типом


Вы также можете использовать std::EqualityComparableWith<T, U> Концепция может проверить эти перегрузки между двумя различными типами.

Есть много других концепций, которые были добавлены в стандарты, такие как Incrementable и т.д.. Посмотрите здесь

Начиная с 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&>()()));
}

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

На этот вопрос уже отвечали несколько раз, но есть более простой способ проверить наличие 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. Конечный результат, конечно, тот же, но это стало моей любимой идиомой для тестирования подобных вещей.

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

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

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

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

Демо онлайн

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

Мы можем использовать std::equal_to<Type> (или любые другие перегруженные элементы структуры), чтобы получить более общее решение.

struct No {};

template<class T, class Operator>
struct ExistsOperator
{
    enum { value = !std::is_same<decltype(std::declval<Operator>()(std::declval<T>(), std::declval<T>())), No>::value };
};

Применение:

using Type = int;
constexpr bool hasEqual = ExistsOperator<Type, std::equal_to<Type>>::value;

Это должно работать на С++11

      template <class Void, template<class...> class Type, class... Args>
struct validator
{
     using value_t = std::false_type;
};

template <template<class...> class Type, class... Args>
struct validator< std::void_t<Type<Args...>>, Type, Args... >
{
     using value_t = std::true_type;
};
 
template <template<class...> class Type, class... Args>
using is_valid = typename validator<void, Type, Args...>::value_t;

template<typename... T>
using has_equal_t = decltype((std::declval<T&>().operator ==(std::declval<T&>()), ...));

template<typename... T>
using has_gequal_t = decltype((operator ==(std::declval<T&>(),std::declval<T&>()), ...));


struct EQ
{
     bool operator==(const EQ&) const;
};

struct GEQ
{
};
bool operator==(const GEQ&, const GEQ&);

struct NOEQ
{
};

static_assert(is_valid<has_equal_t,EQ>::value || is_valid<has_gequal_t,EQ>::value, "should have equal operator");
static_assert(is_valid<has_equal_t,GEQ>::value || is_valid<has_gequal_t,GEQ>::value, "should have equal operator");
// static_assert(is_valid<has_equal_t,NOEQ>::value || is_valid<has_gequal_t,NOEQ>::value, "should have equal operator"); // ERROR:

Помимо ответа @coder3101,conceptsможет помочь вам реализовать любые тесты на существование функций, которые вы хотите. Например,std::equality_comparable реализован с помощью 4 простых тестов, проверяющих следующие сценарии:

За A а также B переменных, убедитесь, что следующие выражения действительны:

A == B, returns bool
A != B, returns bool
B == A, returns bool
B != A, returns bool

Если какой-либо из них недопустим во время компиляции, программа не будет компилироваться. Реализация этого теста (упрощенная от стандарта):

template <typename T> concept equality_comparable
    = requires(T t, T u) {
        { t == u } -> std::convertible_to<bool>;
        { t != u } -> std::convertible_to<bool>;
        { u == t } -> std::convertible_to<bool>;
        { u != t } -> std::convertible_to<bool>;
    };

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

template <typename T> concept my_equality_comparable
    = requires(T t, T u) {
        { t == u } -> std::convertible_to<bool>;
        { u == t } -> std::convertible_to<bool>;
    };

Узнать больше о concepts в C++20.

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

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