Nessun operatore == trovato durante il confronto di strutture in C ++


96

Confrontando due istanze della seguente struttura, ricevo un errore:

struct MyStruct1 {
    MyStruct1(const MyStruct2 &_my_struct_2, const int _an_int = -1) :
        my_struct_2(_my_struct_2),
        an_int(_an_int)
    {}

    std::string toString() const;

    MyStruct2 my_struct_2;
    int an_int;
};

L'errore è:

errore C2678: binario '==': nessun operatore trovato che accetta un operando di sinistra di tipo 'myproj :: MyStruct1' (o non esiste una conversione accettabile)

Perché?

Risposte:


126

In C ++, structs non hanno un operatore di confronto generato per impostazione predefinita. Devi scrivere il tuo:

bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return /* your comparison code goes here */
}

21
@ Jonathan: Perché il C ++ dovrebbe sapere come vuoi confrontare i tuoi messaggi structdi uguaglianza? E se vuoi il modo semplice, c'è sempre memcmpcosì tanto tempo che le tue strutture non contengono puntatore.
Xeo

12
@Xeo: memcmpfallisce con membri non POD (come std::string) e strutture imbottite.
fredoverflow

16
@ Jonathan I linguaggi "moderni" che conosco forniscono un ==operatore --- con una semantica che non è quasi mai quella che si vuole. (E non forniscono un mezzo per sovrascriverlo, quindi finisci per dover usare una funzione membro). Anche i linguaggi "moderni" che conosco non forniscono la semantica dei valori, quindi sei costretto a usare i puntatori, anche quando non sono appropriati.
James Kanze

4
@ Jonathan Cases sicuramente variano, anche all'interno di un dato programma. Per gli oggetti entità, la soluzione fornita da Java funziona molto bene (e, naturalmente, puoi fare esattamente la stessa cosa in C ++ --- è persino idiomatico C ++ per gli oggetti entità). La domanda è cosa fare con gli oggetti valore. C ++ fornisce un valore predefinito operator=(anche se spesso fa la cosa sbagliata), per motivi di compatibilità C. operator==Tuttavia, la compatibilità C non richiede un . A livello globale, preferisco ciò che fa C ++ a ciò che fa Java. (Non so C #, quindi forse è meglio.)
James Kanze

9
Almeno dovrebbe essere possibile = default!
user362515

94

C ++ 20 ha introdotto confronti predefiniti, alias "astronave"operator<=> , che consente di richiedere </ <=/ ==/ !=/ >=/ e / o >operatori generati dal compilatore con l'implementazione ovvia / ingenua (?) ...

auto operator<=>(const MyClass&) const = default;

... ma puoi personalizzarlo per situazioni più complicate (discusso di seguito). Vedi qui per la proposta linguistica, che contiene giustificazioni e discussioni. Questa risposta rimane rilevante per C ++ 17 e versioni precedenti e per informazioni su quando è necessario personalizzare l'implementazione di operator<=>....

Può sembrare un po 'inutile per C ++ non averlo già standardizzato in precedenza, ma spesso le strutture / classi hanno alcuni membri di dati da escludere dal confronto (ad esempio contatori, risultati memorizzati nella cache, capacità del contenitore, codice di errore / successo dell'ultima operazione, cursori), come così come le decisioni da prendere su una miriade di cose tra cui ma non limitate a:

  • quali campi confrontare per primi, ad esempio il confronto di un particolare intmembro potrebbe eliminare il 99% di oggetti disuguali molto rapidamente, mentre un map<string,string>membro potrebbe spesso avere voci identiche ed essere relativamente costoso da confrontare - se i valori vengono caricati in fase di esecuzione, il programmatore potrebbe avere intuizioni il compilatore non è possibile
  • nel confronto di stringhe: distinzione tra maiuscole e minuscole, equivalenza di spazi e separatori, convenzioni di escape ...
  • precisione quando si confrontano float / double
  • se i valori in virgola mobile NaN debbano essere considerati uguali
  • confrontare puntatori o dati puntati (e in quest'ultimo caso, come sapere se i puntatori sono agli array e di quanti oggetti / byte necessitano di confronto)
  • se l'ordine è importante quando si confrontano contenitori non ordinati (ad es vector. list) e, in tal caso, se è ok ordinarli sul posto prima del confronto rispetto all'uso di memoria extra per ordinare i provvisori ogni volta che viene eseguito un confronto
  • quanti elementi dell'array attualmente contengono valori validi che dovrebbero essere confrontati (c'è una dimensione da qualche parte o una sentinella?)
  • quale membro di uniona confrontare
  • normalizzazione: ad esempio, i tipi di data possono consentire un giorno del mese o un mese dell'anno fuori intervallo, oppure un oggetto razionale / frazione può avere 6/8 mentre un altro ha 3/4, che per motivi di prestazioni correggono pigramente con un passaggio di normalizzazione separato; potresti dover decidere se attivare una normalizzazione prima del confronto
  • cosa fare quando i puntatori deboli non sono validi
  • come gestire membri e basi che non si implementano da operator==soli (ma potrebbero avere compare()or operator<or str()or getter ...)
  • quali blocchi devono essere presi durante la lettura / confronto dei dati che altri thread potrebbero voler aggiornare

Quindi, è un po ' bello avere un errore finché non hai pensato esplicitamente a cosa dovrebbe significare il confronto per la tua struttura specifica, piuttosto che lasciarlo compilare ma non darti un risultato significativo in fase di esecuzione .

Detto questo, sarebbe bello se C ++ ti permettesse di dire bool operator==() const = default;quando hai deciso che un ==test "ingenuo" membro per membro era ok. Lo stesso per !=. Attribuite più membri / basi, "default" <, <=, >, e >=le implementazioni sembrano senza speranza anche se - a cascata sulla base dell'ordine di tutto è possibile, ma molto improbabile che sia quello che voleva, dato in conflitto imperativi per gli ordini (basi essendo necessariamente prima che i membri, il raggruppamento mediante dichiarazione accessibilità, costruzione / distruzione prima dell'uso dipendente). Per essere più ampiamente utile, il C ++ avrebbe bisogno di un nuovo sistema di annotazione di base / membro dei dati per guidare le scelte - sarebbe un'ottima cosa avere nello Standard, tuttavia, idealmente accoppiato con la generazione di codice definita dall'utente basata su AST ... esso '

Tipica implementazione degli operatori di uguaglianza

Un'attuazione plausibile

È probabile che un'implementazione ragionevole ed efficiente sia:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.my_struct2 == rhs.my_struct2 &&
           lhs.an_int     == rhs.an_int;
}

Nota che anche questo richiede un operator==per MyStruct2.

Le implicazioni di questa implementazione e le alternative sono discusse nella sezione Discussione delle specifiche di MyStruct1 di seguito.

Un approccio coerente a ==, <,> <= ecc

È facile sfruttare std::tuplegli operatori di confronto di per confrontare le proprie istanze di classe: basta usarle std::tieper creare tuple di riferimenti ai campi nell'ordine di confronto desiderato. Generalizzando il mio esempio da qui :

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) ==
           std::tie(rhs.my_struct2, rhs.an_int);
}

inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) <
           std::tie(rhs.my_struct2, rhs.an_int);
}

// ...etc...

Quando "possiedi" (ovvero puoi modificare un fattore con librerie aziendali e di terze parti) la classe che desideri confrontare, e specialmente con la preparazione di C ++ 14 nel dedurre il tipo di ritorno della funzione dall'istruzione return, è spesso più bello aggiungere un " lega "la funzione membro alla classe che vuoi poter confrontare:

auto tie() const { return std::tie(my_struct1, an_int); }

Quindi i confronti sopra si semplificano per:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.tie() == rhs.tie();
}

Se desideri un set più completo di operatori di confronto, ti suggerisco di aumentare gli operatori (cerca less_than_comparable). Se per qualche motivo non è adatto, l'idea delle macro di supporto (online) potrebbe piacerti o meno :

#define TIED_OP(STRUCT, OP, GET_FIELDS) \
    inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
    { \
        return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
    }

#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
    TIED_OP(STRUCT, ==, GET_FIELDS) \
    TIED_OP(STRUCT, !=, GET_FIELDS) \
    TIED_OP(STRUCT, <, GET_FIELDS) \
    TIED_OP(STRUCT, <=, GET_FIELDS) \
    TIED_OP(STRUCT, >=, GET_FIELDS) \
    TIED_OP(STRUCT, >, GET_FIELDS)

... che può quindi essere utilizzato a la ...

#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)

(C ++ 14 versione con legami di membri qui )

Discussione delle specifiche del tuo MyStruct1

Ci sono delle implicazioni nella scelta di fornire un membro indipendente rispetto a un membro operator==()...

Implementazione indipendente

Hai una decisione interessante da prendere. Poiché la tua classe può essere costruita implicitamente da a MyStruct2, una funzione indipendente / non membro bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)supporterebbe ...

my_MyStruct2 == my_MyStruct1

... creando prima un temporaneo MyStruct1da my_myStruct2, poi facendo il confronto. Ciò lascerebbe sicuramente MyStruct1::an_intimpostato il valore del parametro predefinito del costruttore di -1. A seconda se si include an_intil confronto per l'attuazione della vostra operator==, un MyStruct1potrebbe o non potrebbe confrontare uguale ad una MyStruct2che si confronta uguale al MyStruct1's my_struct_2membro! Inoltre, la creazione di un temporaneo MyStruct1può essere un'operazione molto inefficiente, poiché implica la copia del my_struct2membro esistente in un temporaneo, solo per gettarlo via dopo il confronto. (Ovviamente, potresti impedire questa costruzione implicita di MyStruct1s per il confronto creando quel costruttore explicito rimuovendo il valore predefinito per an_int.)

Implementazione dei membri

Se vuoi evitare la costruzione implicita di a MyStruct1da a MyStruct2, rendi l'operatore di confronto una funzione membro:

struct MyStruct1
{
    ...
    bool operator==(const MyStruct1& rhs) const
    {
        return tie() == rhs.tie(); // or another approach as above
    }
};

Si noti che la constparola chiave, necessaria solo per l'implementazione dei membri, avvisa il compilatore che il confronto degli oggetti non li modifica, quindi può essere consentito sugli constoggetti.

Confronto delle rappresentazioni visibili

A volte il modo più semplice per ottenere il tipo di confronto desiderato può essere ...

    return lhs.to_string() == rhs.to_string();

... che spesso è anche molto costoso - quelli stringcreati dolorosamente solo per essere gettati via! Per i tipi con valori in virgola mobile, confrontare le rappresentazioni visibili significa che il numero di cifre visualizzate determina la tolleranza entro la quale i valori quasi uguali vengono trattati come uguali durante il confronto.


Bene, in realtà per gli operatori di confronto <,>, <=,> = dovrebbe essere richiesto solo di implementare <. Il resto segue e non esiste un modo significativo per implementarli, ovvero qualcosa di diverso dall'implementazione che può essere generata automaticamente. È bizzarro che tu debba implementarli tutti da solo.
André

@ André: più spesso una scritta manualmente int cmp(x, y)o comparefunzione che restituisce un valore negativo per x < y, 0 per la parità e un valore positivo x > yviene utilizzata come base per <, >, <=, >=, ==, e !=; è molto facile usare il CRTP per iniettare tutti quegli operatori in una classe. Sono sicuro di aver pubblicato l'implementazione in una vecchia risposta, ma non sono riuscito a trovarla rapidamente.
Tony Delroy

@TonyD Certo si può fare, ma è altrettanto facile da implementare >, <=e >=dal punto di vista <. Si potrebbe anche implementare ==e !=in quel modo, ma questo non sarà di norma un'implementazione molto efficace immagino. Sarebbe bello se non fossero necessari CRTP o altri trucchi per tutto questo, ma lo standard imporrebbe semplicemente l'auto-generazione di questi operatori se non è esplicitamente definito dall'utente e <viene definito.
André

@ André: è perché ==e !=potrebbe non essere espresso in modo efficiente usando <che l'uso del confronto per tutto è comune. "Sarebbe bello se non CRTP o altri trucchi sarebbero necessari" - forse, ma poi CRTP può essere facilmente utilizzato per generare un sacco di altri operatori (ad esempio bit a bit |, &, ^da |=, &=e ^=, + - * / %dalle loro forme di assegnazione; binario -dalla negazione unaria e +) - così tante variazioni potenzialmente utili su questo tema che fornire solo una caratteristica del linguaggio per una parte piuttosto arbitraria di questo non è particolarmente elegante.
Tony Delroy

Ti dispiacerebbe aggiungere a un'implementazione plausibile una versione che utilizza std::tieper fare il confronto di più membri?
NathanOliver

17

Devi definire esplicitamente operator ==per MyStruct1.

struct MyStruct1 {
  bool operator == (const MyStruct1 &rhs) const
  { /* your logic for comparision between "*this" and "rhs" */ }
};

Ora il confronto == è legale per 2 di questi oggetti.


11

A partire dal C ++ 20, dovrebbe essere possibile aggiungere una serie completa di operatori di confronto di default ( ==, <=, ecc) a una classe dichiarando una a tre vie operatore di confronto predefinito (operatore "navicella spaziale"), in questo modo:

struct Point {
    int x;
    int y;
    auto operator<=>(const Point&) const = default;
};

Con un compilatore C ++ 20 conforme, l'aggiunta di quella riga a MyStruct1 e MyStruct2 può essere sufficiente per consentire confronti di uguaglianza, supponendo che la definizione di MyStruct2 sia compatibile.


2

Il confronto non funziona con le strutture in C o C ++. Confronta invece per campi.


2

Per impostazione predefinita, le strutture non hanno un ==operatore. Dovrai scrivere la tua implementazione:

bool MyStruct1::operator==(const MyStruct1 &other) const {
    ...  // Compare the values, and return a bool result.
  }

0

Per impostazione predefinita, l'operatore == funziona solo per le primitive. Per far funzionare il codice, è necessario sovraccaricare l'operatore == per la struttura.


0

Perché non hai scritto un operatore di confronto per la tua struttura. Il compilatore non lo genera per te, quindi se vuoi un confronto, devi scriverlo da solo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.