Come verificare se l’operatore == esiste?

Sto cercando di creare un esempio, che verificherebbe l’esistenza operator== (membro o funzione non membro). Per verificare se una class ha un operator== membro operator== è facile, ma come verificare se ha un operator== non membro operator== ?

Questo è quello che devo fare lontano:

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

È ansible scrivere una funzione di test per verificare l’esistenza di un operator== non membro operator== ? Se sì, come?

btw Ho controllato domande simili, ma non ho trovato una soluzione adeguata:
È ansible utilizzare SFINAE / templates per verificare se esiste un operatore?

Questo è quello che ho provato:

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

ma la compilazione fallisce se l’operatore non membro == viene rimosso

C ++ 03

Seguendo i trucchi. E può essere usato per tutti questi operatori:

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

Uso:

 CHECK::EqualExists::value; 

Il secondo template typename Arg è utile per alcuni casi speciali come A::operator==(short) , dove non è simile alla class stessa. In questi casi l’utilizzo è:

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

Demo .


C ++ 11

Non abbiamo bisogno di usare sizeof trick quando abbiamo decltype

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

dimostrazione

Dai un’occhiata alla Concept Check Library di Boost (BCCL) http://www.boost.org/doc/libs/1_46_1/libs/concept_check/concept_check.htm .

Ti consente di scrivere i requisiti che una class deve soddisfare affinché il programma possa essere compilato. Sei relativamente libero con quello che puoi controllare. Ad esempio, verificando la presenza operator== di una class Foo si scriverebbe come segue:

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

Questo codice viene compilato correttamente purché sia ​​disponibile una delle due implementazioni operator== .

Seguendo i consigli di @Matthieu M. e @Luc Touraille, ho aggiornato lo snippet di codice per fornire un esempio di utilizzo di boost::EqualityComparable . Ancora una volta, tieni presente che EqualityComparable ti costringe a dichiarare l’ operator!= Anche.

È anche ansible utilizzare solo i tratti di tipo c ++ 11 per verificare l’esistenza del membro:

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

Puoi usare il tratto in questo modo:

 bool test = has_operator_equal::value; 

Il tipo risultante di has_operator_equal sarà std::true_type o std::false_type (poiché eredita da un alias di std::is_same::type ) ed entrambi definiscono un membro di value statico che è un valore booleano.


Se vuoi essere in grado di verificare se la tua class definisce operator==(someOtherType) , puoi impostare il secondo argomento template:

 bool test = has_operator_equal::value; 

dove il parametro template MyClass è ancora la class che stai testando per la presenza operator== , e long è il tipo a cui vuoi essere in grado di confrontare, ad esempio per verificare che MyClass abbia operator==(long) .

se EqualTo (come nel primo esempio) non è specificato, verrà impostato su T , EqualTo la normale definizione di operator==(MyClass) .

Nota di caucanvas : questa caratteristica nel caso operator==(long) sarà vera per long , o qualsiasi valore implicitamente convertibile in long , ad es. double , int , ecc.


Puoi anche definire i controlli per altri operatori e funzioni, semplicemente sostituendo ciò che è all’interno del decltype . Per verificare != , È sufficiente sostituire

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

con

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

A partire da c ++ 14, le funzioni binarie standard fanno la maggior parte del lavoro per noi per la maggior parte degli operatori.

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

So che questa domanda è stata a lungo risposta, ma ho pensato che valesse la pena di notare per chiunque trova questa domanda in futuro che Boost ha appena aggiunto una serie di tratti “ha operatore” alla loro libreria type_traits, e tra questi c’è has_equal_to , che fa quello che OP stava chiedendo.

Questa domanda ha già avuto risposta diverse volte, ma esiste un modo più semplice per verificare l’esistenza operator== o fondamentalmente qualsiasi altra operazione (es. decltype una funzione membro con un certo nome), usando decltype insieme a , operatore:

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

Puoi usare questo stesso approccio per verificare se un tipo T ha una funzione membro foo che è invocabile con un certo elenco di argomenti:

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

Penso che questo renda l’intento del codice molto più chiaro. In aggiunta a ciò, questa è una soluzione C ++ 11, quindi non dipende da nuove funzionalità di C ++ 14 o C ++ 17. Il risultato finale è lo stesso, ovviamente, ma questo è diventato il mio idioma preferito per testare questo tipo di cose.

Modifica: risolto il caso folle dell’operatore virgola sovraccarico, mi manca sempre.

Solo per un riferimento, sto pubblicando come ho risolto il mio problema, senza la necessità di verificare se l’ operator== esiste:

 #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, questo deve essere parte della class stessa poiché si occupa degli attributi privati ​​della class. I modelli sono interpretati in fase di compilazione. Per impostazione predefinita genera operator== , costruttore, distruttore e costruttore di copie che eseguono il confronto bit-saggio (copia superficiale) o bit-bit per l’object dello stesso tipo. I casi speciali (tipi diversi) devono essere sovraccaricati. Se si utilizza la funzione di operatore globale, è necessario dichiarare la funzione come amico per accedere alla parte privata oppure è necessario esporre le interfacce richieste. A volte questo è davvero brutto che può causare un’esposizione non necessaria di una funzione.

Consideriamo una meta-funzione del seguente modulo, che verifica l’esistenza dell’operatore di uguaglianza (es. == ) per il tipo dato:

 template struct equality { .... }; 

Tuttavia, potrebbe non essere sufficiente per alcuni casi d’angolo. Ad esempio, supponiamo che la tua class X definisca operator== ma non restituisca bool , invece restituisce Y Quindi, in questo caso, cosa dovrebbe restituire l’ equality::value ? true o false ? Bene, questo dipende dal caso d’uso specifico che non conosciamo ora, e non sembra essere una buona idea supporre qualcosa e forzarlo sugli utenti. Tuttavia, in generale, possiamo supporre che il tipo restituito debba essere bool , quindi lasciamolo esprimere nell’interfaccia stessa:

 template struct equality { .... }; 

Il valore predefinito per R è bool che indica che è il caso generale. Nei casi in cui il tipo di operator== di ritorno operator== è diverso, ad esempio Y , allora puoi dire questo:

 equality //return type = Y 

che controlla anche il tipo restituito. Di default,

 equality //return type = bool 

Ecco una implementazione di questa meta-funzione:

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

Test:

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

Produzione:

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

Demo online

Spero possa aiutare.

c ++ 17 versione leggermente modificata del diobollo di 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);