Perché la disuguaglianza è testata come (! (A == b)) in molti codici di libreria standard C ++?


142

Questo è il codice dal codice della libreria standard C ++ remove. Perché la disuguaglianza è testata come if (!(*first == val))anziché if (*first != val)?

 template <class ForwardIterator, class T>
      ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val)
 {
     ForwardIterator result = first;
     while (first!=last) {
         if (!(*first == val)) {
             *result = *first;
             ++result;
         }
         ++first;
     }
     return result;
 }

2
@BeyelerStudios è probabilmente corretto. È anche comune durante l'implementazione operator!=. Basta usare l' operator==implementazione:bool operator!=(const Foo& other) { return !(*this == other); }
simon

1
in realtà correggo la mia affermazione: la menzione dei riferimenti rimuove tutti gli elementi uguali al valore, quindi operator==si prevede che vengano usati qui ...
BeyelerStudios,

Oh, e dovrebbe esserci anche un constesempio nell'esempio nel mio commento precedente, ma ottieni il punto. (Troppo tardi per modificarlo)
simon

La ragione di ciò è legata a un'altra domanda (che può sostanzialmente rispondere con "No, non necessariamente"), e al concetto di essere EqualityComparableche Hurkyl ha menzionato nella sua risposta .
Marco13

Risposte:


144

Perché questo significa che l'unico requisito su T è implementare un operator==. Potresti richiedere a T di avere un'idea, operator!=ma l'idea generale qui è che dovresti mettere il minor onere possibile sull'utilizzatore del modello e che altri modelli hanno bisogno operator==.


13
template <class T> inline bool operator! = <T a, T b> {return! (a == b); }
Giosuè,

8
Ci sarebbe uno scenario in cui un compilatore non sarebbe in grado di scambiare tutte le istanze di =! a! (==)? Perché questa non dovrebbe essere già l'azione predefinita che deve essere eseguita dal compilatore?
Aidan Gomez,

20
@AidanGomez Nel bene o nel male puoi sovraccaricare gli operatori per fare quello che vuoi. Non deve avere senso o essere coerente.
Neil Kirk,

10
x != ynon è definito come lo stesso !(x == y). Cosa succede se questi operatori restituiscono l'albero di analisi di un DSL incorporato?
Brice M. Dempsey,

7
@Joshua Ciò si interrompe gravemente se si tenta di utilizzare SFINAE per rilevare se !=è supportato (restituire erroneamente true - anche se operator==non è supportato!). Sono anche preoccupato che alcuni ambiti !=diventino ambigui.

36

La maggior parte delle funzioni in STL funziona solo con operator<o operator==. Ciò richiede all'utente di implementare solo questi due operatori (o talvolta almeno uno di essi). Ad esempio std::setutilizza operator<(più precisamente std::lessche invoca operator<per impostazione predefinita) e non operator>gestire l'ordinamento. Il removemodello nel tuo esempio è un caso simile: utilizza solo operator==e non è operator!=quindi operator!=necessario definire.


2
Le funzioni non usano operator<direttamente ma invece usano std::less, cosa che a sua volta viene impostata automaticamente operator<.
Christian Hackl,

1
In realtà, sembra che le funzioni dell'algoritmo standard, diversamente dall'esempio std::set, lo utilizzino operator<direttamente. Strano ...
Christian Hackl,

1
Queste funzioni non usano std::equal_to, usanooperator== come indicato nella domanda. La situazione con std::lessè simile. Beh, forse std::setnon è il miglior esempio.
Lukáš Bednařík,

2
@ChristianHackl std::equal_toe std::lessvengono utilizzati come parametri di modello predefiniti in cui il comparatore viene considerato come parametro.operator==e operator<sono utilizzati direttamente dove è richiesto il tipo per soddisfare rispettivamente l'uguaglianza comparabile e un ordinamento debole rigoroso, ad esempio iteratori e iteratori ad accesso casuale.
Jan Hudec,

28

Questo è il codice del codice di rimozione della libreria standard C ++.

Sbagliato. Non è ilremove codice della libreria standard C ++ . È una possibile implementazione interna della removefunzione di libreria standard C ++ . Lo standard C ++ non prescrive il codice effettivo; prescrive prototipi funzionali e comportamenti richiesti.

In altre parole: da un punto di vista linguistico rigoroso, il codice che vedi non esiste . Potrebbe provenire da un file di intestazione fornito con l'implementazione della libreria standard del compilatore. Si noti che lo standard C ++ non richiede nemmeno l' esistenza di quei file di intestazione . I file sono solo un modo conveniente per gli implementatori di compilatori di soddisfare i requisiti di una linea simile #include <algorithm>(ovvero rendere disponibili std::removee altre funzioni).

Perché la disuguaglianza è testata come if (!(*first == val))anziché if (*first != val)?

Perché operator==è richiesto solo dalla funzione.

Quando si tratta di sovraccarico dell'operatore per tipi personalizzati, la lingua ti consente di fare tutti i tipi di cose strane. Si potrebbe benissimo creare una classe che ha un sovraccarico operator==ma non sovraccarico operator!=. O peggio ancora: potresti sovraccaricarti operator!=ma fare cose completamente indipendenti.

Considera questo esempio:

#include <algorithm>
#include <vector>

struct Example
{
    int i;

    Example() : i(0) {}

    bool operator==(Example const& other) const
    {
        return i == other.i;
    }

    bool operator!=(Example const& other) const
    {
        return i == 5; // weird, but nothing stops you
                       // from doing so
    }

};

int main()
{
  std::vector<Example> v(10);
  // ...
  auto it = std::remove(v.begin(), v.end(), Example());
  // ...
}

Se std::removeutilizzato operator!=, il risultato sarebbe piuttosto diverso.


1
Un'altra cosa da considerare è che potrebbe essere possibile per entrambi a==be a!=brestituire false. Anche se potrebbe non essere sempre chiaro se una situazione del genere venga considerata in modo più significativo come "uguale" o "non uguale", una funzione che definisce l'uguaglianza esclusivamente in termini di operatore "==" deve considerarli come "non uguali" ", indipendentemente dal comportamento che avrebbe più senso [se avessi i miei druther, ci si aspetterebbe che tutti i tipi facciano comportare gli operatori" == "e"! = "con rendimento booleano, ma il fatto che IEEE-754 imponga l'uguaglianza rotta gli operatori renderebbero difficile tale aspettativa].
supercat

18
"Da un punto di vista linguistico rigoroso, il codice che vedi non esiste." - quando un punto di vista dice che qualcosa non esiste, ma in realtà stai guardando quella cosa, allora il punto di vista è sbagliato. In effetti lo standard non dice che il codice non esiste, semplicemente non dice che esiste :-)
Steve Jessop

@SteveJessop: puoi sostituire l'espressione "da un punto di vista linguistico rigoroso" con qualcosa come "rigorosamente, a livello linguistico". Il punto è che il codice pubblicato dall'OP non è una preoccupazione dello standard linguistico.
Christian Hackl,

2
@supercat: IEEE-754 crea ==e si !=comporta in modo coerente, anche se ho sempre pensato che tutte e sei le relazioni dovrebbero essere valutate falsequando almeno un operando lo è NaN.
Ben Voigt,

@BenVoigt: Ah, è vero. Fa sì che i due operatori si comportino nello stesso modo rotto in modo tale da essere coerenti tra loro, ma riescono comunque a violare tutti gli altri assiomi associati all'equivalenza (ad esempio, non sostengono né a == a, né la garanzia che le operazioni eseguite su valori uguali produrrà risultati uguali).
supercat

15

Alcune buone risposte qui. Volevo solo aggiungere una piccola nota.

Come tutte le buone librerie, la libreria standard è progettata con (almeno) due principi molto importanti in mente:

  1. Metti la minima quantità di responsabilità sugli utenti della tua biblioteca con cui puoi cavartela. Parte di questo ha a che fare con il dare loro il minor numero di lavoro da fare quando si utilizza l'interfaccia. (come definire il minor numero di operatori con cui puoi cavartela). L'altra parte ha a che fare con non sorprenderli o richiedere loro di controllare i codici di errore (quindi mantenere coerenti le interfacce e gettare eccezioni da <stdexcept>quando le cose vanno male).

  2. Elimina tutta la ridondanza logica . Tutti i confronti possono essere dedotti semplicemente da operator<, quindi perché chiedere agli utenti di definirne altri? per esempio:

    (a> b) è equivalente a (b <a)

    (a> = b) è equivalente a! (a <b)

    (a == b) è equivalente a! ((a <b) || (b <a))

    e così via.

    Naturalmente su questa nota, ci si potrebbe chiedere perché unordered_maprichiede operator==(almeno per impostazione predefinita) piuttosto che operator<. La risposta è che in una tabella hash l'unico confronto che abbiamo mai richiesto è uno per l'uguaglianza. Quindi è logicamente più coerente (cioè ha più senso per l'utente della biblioteca) richiedere loro di definire un operatore di uguaglianza. Richiedere un operator<sarebbe confuso perché non è immediatamente ovvio il motivo per cui ne avresti bisogno.


10
Per quanto riguarda il tuo secondo punto: ci sono tipi che possono essere logicamente confrontati per l'uguaglianza anche se non esiste un ordine logico o per i quali stabilire un ordine sarebbe molto artificiale. Ad esempio, il rosso è rosso e il rosso non è verde, ma il rosso è intrinsecamente inferiore al verde?
Christian Hackl,

1
Sono completamente d'accordo. Uno non memorizzerebbe questi articoli in un contenitore ordinato perché non esiste un ordine logico. Potrebbero essere più opportunamente conservati in un contenitore non ordinato che richiede operator==(e hash).
Richard Hodges,

3. Sovraccaricare meno operatori standard possibili. Questo è un altro motivo che hanno implementato !(a==b). Poiché il sovraccarico non riflesso degli operatori può facilmente causare la confusione totale del programma C ++ (inoltre, far impazzire il programmatore perché il debug del suo codice può diventare una missione impossibile, dal momento che trovare il colpevole di un determinato bug è simile a un'odissea).
syntaxerror,

!((a < b) || (b < a))usa un operatore bool in meno, quindi è probabilmente più veloce
Filip Haglund,

1
In realtà non lo fanno. Nel linguaggio assembly tutti i confronti sono implementati come sottrazione seguita da test carry e zero bit nel registro flag. Tutto il resto è solo zucchero sintattico.
Richard Hodges,

8

Il EqualityComparableconcetto richiede solo questooperator== sia definito.

Di conseguenza, qualsiasi funzione che professa di lavorare con tipi soddisfacenti EqualityComparable non può fare affidamento sull'esistenza di operator!=oggetti di quel tipo. (a meno che non vi siano requisiti aggiuntivi che implicano l'esistenza di operator!=).


1

L'approccio più promettente è trovare un metodo per determinare se l'operatore == può essere chiamato per un tipo particolare e quindi supportarlo solo quando è disponibile; in altre situazioni, verrebbe generata un'eccezione. Tuttavia, ad oggi non è noto alcun modo per rilevare se un'espressione di operatore arbitrario f == g è adeguatamente definita. La migliore soluzione conosciuta ha le seguenti qualità indesiderabili:

  • Errore in fase di compilazione per oggetti in cui operator == non è accessibile (ad es. Perché è privato).
  • Errore durante la compilazione se chiamare l'operatore == è ambiguo.
  • Sembra essere corretto se l'operatore == la dichiarazione è corretta, anche se l'operatore == potrebbe non essere compilato.

Da Boost FAQ: fonte

Sapendo che richiedere l' ==implementazione è un onere , non si vuole mai creare un onere aggiuntivo richiedendo!= implementazione.

Per me personalmente si tratta di SOLID (design orientato agli oggetti) Parte L - Principio di sostituzione di Liskov: "gli oggetti in un programma dovrebbero essere sostituibili con istanze dei loro sottotipi senza alterare la correttezza di quel programma.". In questo caso è l'operatore ! = Che posso sostituire con == e l'inverso booleano nella logica booleana.

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.