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

J’essaie de créer un exemple, qui vérifierait l’existence de l’ operator== (membre ou fonction non-membre). Pour vérifier si une classe a un operator== membre operator== est facile, mais comment vérifier si elle a un operator== non membre operator== ?

C’est ce que j’ai à faire:

 #include  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 ( la==ra); } #endif template  struct opEqualExists { struct yes{ char a[1]; }; struct no { char a[2]; }; template  static yes test( typeof(&C::operator==) ); //template  static yes test( ???? ); template  static no test(...); enum { value = (sizeof(test(0)) == sizeof(yes)) }; }; int main() { std::cout<<(int)opEqualExists::value<<std::endl; } 

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

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

C’est ce que j’ai essayé:

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

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

C ++ 03

L’astuce suivante fonctionne. Et il peut être utilisé pour tous ces opérateurs:

 namespace CHECK { class No { bool b[2]; }; template No operator== (const T&, const Arg&); bool Check (...); No& Check (const No&); template  struct EqualExists { enum { value = (sizeof(Check(*(T*)(0) == *(Arg*)(0))) != sizeof(No)) }; }; } 

Usage:

 CHECK::EqualExists::value; 

Le 2ème template typename Arg est utile pour certains cas particuliers comme A::operator==(short) , où il n’est pas similaire à la class elle class même. Dans de tels cas, l’utilisation est la suivante:

 CHECK::EqualExists::value // ^^^^^ argument of `operator==` 

Démo .


C ++ 11

Nous n’avons pas besoin d’utiliser sizeof sortingck lorsque nous avons decltype

 namespace CHECK { struct No {}; template No operator== (const T&, const Arg&); template struct EqualExists { enum { value = !std::is_same::value }; }; } 

Démo

Jetez un coup d’oeil à la bibliothèque de vérification de concept de Boost (BCCL) http://www.boost.org/doc/libs/1_46_1/libs/concept_check/concept_check.htm .

Il 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 l’ operator== d’une classe Foo écrirait comme suit:

 #include  template  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  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)); BOOST_CONCEPT_ASSERT((boost::EqualityComparable)); // need operator!= too } 

Ce code comstack bien tant que l’une des deux implémentations d’ operator== est disponible.

Suite aux conseils de @Matthieu M. et @Luc Touraille, j’ai mis à jour l’extrait de code pour donner un exemple d’utilisation boost::EqualityComparable . Encore une fois, veuillez noter que EqualityComparable vous oblige à déclarer operator!= Aussi.

Il est également possible d’utiliser uniquement des caractères de type c ++ 11 pour vérifier l’existence du membre:

 #include  #include  template struct has_operator_equal_impl { template static auto test(U*) -> decltype(std::declval() == std::declval()); template static auto test(...) -> std::false_type; using type = typename std::is_same(0))>::type; }; template struct has_operator_equal : has_operator_equal_impl::type {}; 

Vous pouvez utiliser le trait comme ça:

 bool test = has_operator_equal::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 tous deux définissent un membre de value statique qui est un booléen.


Si vous voulez être en mesure de tester si votre classe définit operator==(someOtherType) , vous pouvez définir le second argument:

 bool test = has_operator_equal::value; 

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

Si EqualTo (comme dans le premier exemple) n’est pas spécifié, il sera défini par défaut sur T , ce qui donnera la définition normale d’ operator==(MyClass) .

Note de prudence : Ce trait dans le cas de l’ operator==(long) sera vrai pour long , ou toute valeur implicitement convertible en long , par exemple double , int , etc.


Vous pouvez également définir des vérifications pour d’autres opérateurs et fonctions, simplement en remplaçant ce qui se trouve dans le decltype . Pour vérifier != , Remplacez simplement

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

avec

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

Depuis c ++ 14, les fonctions binarys standard font le gros du travail pour nous pour la majorité des opérateurs.

 #include  #include  #include  #include  #include  template struct op_valid_impl { template static auto test(int) -> decltype(std::declval()(std::declval(), std::declval()), void(), std::true_type()); template static auto test(...) -> std::false_type; using type = decltype(test(0)); }; template using op_valid = typename op_valid_impl::type; namespace notstd { struct left_shift { template  constexpr auto operator()(L&& l, R&& r) const noexcept(noexcept(std::forward(l) << std::forward(r))) -> decltype(std::forward(l) << std::forward(r)) { return std::forward(l) << std::forward(r); } }; struct right_shift { template  constexpr auto operator()(L&& l, R&& r) const noexcept(noexcept(std::forward(l) >> std::forward(r))) -> decltype(std::forward(l) >> std::forward(r)) { return std::forward(l) >> std::forward(r); } }; } template using has_equality = op_valid>; template using has_inequality = op_valid>; template using has_less_than = op_valid>; template using has_less_equal = op_valid>; template using has_greater_than = op_valid>; template using has_greater_equal = op_valid>; template using has_bit_xor = op_valid>; template using has_bit_or = op_valid>; template using has_left_shift = op_valid; template using has_right_shift = op_valid; int main() { assert(( has_equality() )); assert((not has_equality()())); assert((has_equality()())); assert(( has_inequality() )); assert(( has_less_than() )); assert(( has_greater_than() )); assert(( has_left_shift() )); assert(( has_left_shift() )); assert(( has_left_shift() )); assert((not has_right_shift()())); assert((has_right_shift()())); assert((not has_right_shift()())); } 

Je sais que cette question a depuis longtemps été résolue mais je pensais que cela valait la peine de noter pour quiconque trouvera cette question dans le futur que Boost vient d’append un tas de traits “operateur” à leur bibliothèque type_traits, et parmi eux has_equal_to , qui fait ce que OP demandait.

On a déjà répondu à cette question à plusieurs resockets, mais il existe un moyen plus simple de vérifier l’existence d’ operator== ou de toute autre opération (par exemple, tester une fonction membre avec un certain nom), en utilisant decltype avec le , opérateur:

 namespace detail { template struct has_operator_equals_impl { template // 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(), std::declval())); }; } // namespace detail template struct has_operator_equals : detail::has_operator_equals_impl::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 liste d’arguments donnée:

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

Je pense que cela rend l’intention du code plus claire. 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 langage préféré pour tester ce genre de choses.

Edit: Correction du casse fou de l’opérateur de virgule surchargé, ça me manque toujours.

Juste pour une référence, je publie comment j’ai résolu mon problème, sans avoir à vérifier si l’ operator== existe:

 #include  #include  struct A { int a; char b; #if 0 bool operator==( const A& r ) const { std::cout<<"calling member function"< 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=="< 

IMO, cela doit faire partie de la classe elle-même car elle traite des atsortingbuts privés de la classe. Les modèles sont interprétés au moment de la compilation. Par défaut, il génère un constructeur operator== , un constructeur, un destructeur et un constructeur de copie qui effectuent une copie par bit (copie superficielle) ou des comparaisons par bit pour l’object du même type. Les cas particuliers (différents types) doivent être surchargés. Si vous utilisez la fonction opérateur globale, vous devrez déclarer la fonction ami pour accéder à la partie privée ou exposer les interfaces requirejses. Parfois, c’est vraiment moche, ce qui peut causer une exposition inutile d’une fonction.

Considérons une méta-fonction du formulaire suivant, qui vérifie l’existence d’un opérateur d’égalité (c’est-à-dire == ) pour le type donné:

 template struct equality { .... }; 

Cependant, cela pourrait ne pas être suffisant pour certains cas en coin. Par exemple, supposons que votre classe X définisse l’ operator== mais qu’elle ne renvoie pas bool , mais renvoie Y Alors, dans ce cas, que devrait être l’ equality::value ? true ou false ? Eh bien, cela dépend du cas d’utilisation spécifique que nous ne connaissons pas maintenant, et cela ne semble pas être une bonne idée d’assumer quoi que ce soit et de forcer les utilisateurs. Cependant, en général, on peut supposer que le type de retour devrait être bool , donc exprimons-le dans l’interface elle-même:

 template struct equality { .... }; 

La valeur par défaut pour R est bool ce qui indique que c’est le cas général. Dans les cas où le type de retour de l’ operator== est différent, dites Y , alors vous pouvez dire ceci:

 equality //return type = Y 

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

 equality //return type = bool 

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

 namespace details { template  struct equality : std::false_type {}; template  struct equality()==std::declval())> : std::true_type {}; } template struct equality : details::equality {}; 

Tester:

 struct A {}; struct B { bool operator == (B const &); }; struct C { short operator == (C const &); }; int main() { std::cout<< "equality::value = " << equality::value << std::endl; std::cout<< "equality::value = " << equality::value << std::endl; std::cout<< "equality::value = " << equality::value << std::endl; std::cout<< "equality::value = " << equality::value << std::endl; std::cout<< "equality::value = " << equality::value << std::endl; } 

Sortie:

 equality::value = 0 equality::value = 1 equality::value = 0 equality::value = 0 equality::value = 1 

Démo en ligne

J'espère que cela pourra aider.

c ++ 17 version légèrement modifiée du godbolt de Richard Hodges

 #include  #include  template std::is_convertible, R> is_invokable_test(int); template std::false_type is_invokable_test(...); template using is_invokable = decltype(is_invokable_test(0)); template constexpr auto is_invokable_v = is_invokable::value; template using has_equality = is_invokable, bool, L, R>; template constexpr auto has_equality_v = has_equality::value; struct L{}; int operator ==(int, L&&); static_assert(has_equality_v); static_assert(!has_equality_v); static_assert(!has_equality_v); static_assert(has_equality_v);