web-dev-qa-db-fra.com

Comment vérifier si l'opérateur == existe?

J'essaie de créer un exemple permettant de vérifier l'existence du operator== (fonction membre ou non membre). Vérifier si une classe a un membre operator== est facile, mais comment vérifier si elle a un operator== non membre?

C'est ce que j'ai à trop loin:

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

Est-il possible d'écrire une fonction de test pour tester l'existence de operator== non membre? Si oui comment?

j'ai vérifié des questions similaires, mais je n'ai pas trouvé de solution adéquate:
Est-il possible d’utiliser SFINAE/templates pour vérifier s’il existe un opérateur?

C'est ce que j'ai essayé:

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

mais la compilation échoue si l'opérateur non membre == est supprimé

44
BЈовић

C++ 03

Le tour suivant fonctionne. Et il peut être utilisé pour tous ces opérateurs:

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

Usage:

CHECK::EqualExists<A>::value;

Le 2nd template typename Arg est utile dans certains cas particuliers tels que A::operator==(short), où il n’est pas similaire à class. Dans de tels cas, l'utilisation est:

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

Démo .


C++ 11

Nous n'avons pas besoin d'utiliser sizeof astuce lorsque nous avons 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 };
  };  
}

Démo

35
iammilind

Jetez un coup d'œil à La bibliothèque de vérification du concept de Boost (BCCL) http://www.boost.org/doc/libs/1_46_1/libs/concept_check/concept_check.htm .

Cela vous permet d'écrire les exigences auxquelles une classe doit correspondre pour que le programme puisse être compilé. Vous êtes relativement libre avec ce que vous pouvez vérifier. Par exemple, vérifier la présence de operator== d'une classe Foo écrira comme suit:

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

Ce code se compile bien tant qu'une des deux implémentations de operator== est disponible.

Suivant les conseils de @Matthieu M. et @Luc Touraille, j'ai mis à jour l'extrait de code afin de fournir un exemple d'utilisation de boost::EqualityComparable. Veuillez noter à nouveau qu'EgalityComparable vous oblige à déclarer également operator!=.

16
jopasserat

Il est également possible d'utiliser uniquement des traits de type c ++ 11 pour vérifier l'existence du membre:

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

Vous pouvez utiliser le trait comme suit:

bool test = has_operator_equal<MyClass>::value;

Le type résultant de has_operator_equal sera soit std::true_type OU std::false_type (car il hérite d'un alias de std::is_same::type), et définissent tous deux un membre value statique qui est un booléen.


Si vous voulez pouvoir vérifier si votre classe définit operator==(someOtherType), vous pouvez définir le deuxième argument du modèle:

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

où le paramètre de modèle MyClass est toujours la classe que vous testez pour la présence de operator==, et long est le type que vous souhaitez pouvoir comparer, par exemple. pour vérifier que MyClass a operator==(long).

si EqualTo (comme c'était le cas dans le premier exemple) n'est pas spécifié, la valeur par défaut sera T, ce qui donnera la définition normale de operator==(MyClass).

Note d'avertissement : Dans le cas de operator==(long), cette caractéristique sera vraie pour long ou toute valeur implicitement convertible en long, par ex. double, int, etc.


Vous pouvez également définir des contrôles pour d'autres opérateurs et fonctions, simplement en remplaçant le contenu de la variable decltype. Pour vérifier !=, remplacez simplement

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

avec

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

À partir de c ++ 14, les fonctions binaires standard effectuent la majeure partie du travail pour nous pour la majorité des opérateurs.

#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
Richard Hodges

Plusieurs réponses ont déjà été apportées à cette question, mais il existe un moyen plus simple de vérifier l'existence de operator== ou de toute autre opération (par exemple, tester une fonction membre avec un certain nom), en utilisant decltype avec l'opérateur ,:

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

Vous pouvez utiliser cette même approche pour vérifier si un type T a une fonction membre foo qui est invocable avec une certaine liste d'arguments:

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

Je pense que cela clarifie beaucoup l’intention du code. En plus de cela, il s’agit d’une solution C++ 11, elle ne dépend donc pas des nouvelles fonctionnalités C++ 14 ou C++ 17. Le résultat final est le même, bien sûr, mais cela est devenu mon idiome préféré pour tester ce genre de choses.

Edit: - Correction du cas insensé de l'opérateur de virgule surchargé, ça me manque toujours.

2
monkey0506

Je sais que cette question a été répondue depuis longtemps, mais j’ai pensé qu’il serait utile de noter pour ceux qui découvrent cette question à l’avenir que Boost vient d’ajouter une série de traits "has operator" à leur bibliothèque type_traits, et parmi eux se trouve has_equal_to. , qui fait ce que OP demandait. 

1
crudcore

Juste pour référence, je montre comment j'ai résolu mon problème, sans qu'il soit nécessaire de vérifier si le operator== existe: 

#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
BЈовић

OMI, cela doit faire partie de la classe elle-même car elle traite des attributs privés de la classe. Les modèles sont interprétés à la compilation. Par défaut, il génère operator==, constructeur, destructeur et constructeur de copie qui effectuent des comparaisons au niveau de la copie (copie peu profonde) ou au niveau du bit pour l'objet de même type. Les cas spéciaux (différents types) doivent être surchargés. Si vous utilisez la fonction d'opérateur global, vous devez déclarer la fonction en tant qu'ami pour accéder à la partie privée, sinon vous devez exposer les interfaces requises. Parfois, c'est vraiment moche, ce qui peut entraîner une exposition inutile d'une fonction.

0
sarat

Considérons une méta-fonction de la forme suivante, qui vérifie l'existence d'un opérateur d'égalité (i.e ==) pour le type donné:

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

Cependant, cela pourrait ne pas suffire dans certains cas. Par exemple, supposons que votre classe X définisse operator== mais qu'elle ne renvoie pas bool, mais plutôt Y. Donc, dans ce cas, que devrait renvoyer equality<X>::value? true ou false? Cela dépend du cas d'utilisation que nous ne connaissons pas encore, et il ne semble pas judicieux d'assumer quoi que ce soit et de le forcer de force. Cependant, en général, nous pouvons supposer que le type de retour doit être bool, aussi exprimons ceci dans l'interface elle-même:

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

La valeur par défaut de R est bool, ce qui indique qu'il s'agit du cas général. Dans les cas où le type de retour de operator== est différent, par exemple, Y, vous pouvez dire ceci:

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

qui vérifie également le type de retour donné. Par défaut,

equality<X>   //return type = bool

Voici une implémentation de cette méta-fonction:

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

Tester:

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

Sortie:

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

Démo en ligne

J'espère que cela pourra aider.

0
Nawaz

c ++ 17 version légèrement modifiée de Richard Hodges 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>);
0
Anton Dyachenko