2015-12-30 10 views
14

Una piccola panoramica. Sto scrivendo un modello di classe che fornisce un typedef forte; da forte typedef sto contrastando con un typedef normale che dichiara semplicemente un alias. Per dare un'idea:Perché gli operatori di confronto std :: vector e std :: string sono definiti come funzioni modello?

using EmployeeId = StrongTypedef<int>; 

Ora, ci sono diverse scuole di pensiero su forti typedef e conversioni implicite. Una di queste scuole dice: non tutti i numeri interi sono EmployeeId, ma ogni EmployeeId è un numero intero, quindi è necessario consentire conversioni implicite da EmployeeId a interi. E si può implementare questa, e scrivere le cose come:

EmployeeId x(4); 
assert(x == 4); 

Questo funziona perché x ottiene implicitamente convertito in un intero, e quindi viene utilizzato il confronto intero uguaglianza. Fin qui tutto bene. Ora, voglio fare questo con un vettore di interi:

using EmployeeScores = StrongTypedef<std::vector<int>>; 

modo che io possa fare cose come questa:

std::vector<int> v1{1,2}; 
EmployeeScores e(v1); 
std::vector<int> v2(e); // implicit conversion 
assert(v1 == v2); 

Ma non riesco ancora a fare questo:

assert(v1 == e); 

Il motivo per cui questo non funziona dipende da come std::vector definisce il controllo di uguaglianza, fondamentalmente (modulo standardese):

template <class T, class A> 
bool operator==(const vector<T,A> & v1, const vector<T,A> & v2) { 
... 
} 

Questo è un modello di funzione; poiché viene scartato in una fase precedente di ricerca, non consentirà un tipo che converte implicitamente in vettoriale per essere confrontato.

Un modo diverso per definire parità sarebbe come questo:

template <class T, class A = std::allocator<T>> 
class vector { 
... // body 

    friend bool operator==(const vector & v1, const vector & v2) { 
    ... 
} 

} // end of class vector 

In questo secondo caso, l'operatore di uguaglianza non è un modello di funzione, è solo una funzione regolare che è generato insieme alla classe, simile a una funzione membro. Questo è un caso insolito abilitato dalla parola chiave friend.

La domanda (mi dispiace lo sfondo è stato così lungo), è perché non std::vector utilizzare il secondo modulo invece del primo? Questo rende vector più simile ai tipi primitivi e, come puoi vedere chiaramente, aiuta nel mio caso d'uso. Questo comportamento è ancora più sorprendente con string poiché è facile dimenticare che string è solo un typedef di un modello di classe.

Due cose che ho considerato: in primo luogo, alcuni potrebbero pensare che poiché la funzione friend viene generata con la classe, ciò causerà un errore grave se il tipo contenuto del vettore non supporta il confronto di uguaglianza. Questo non è il caso; come le funzioni membro delle classi template, non vengono generate se non utilizzate. In secondo luogo, nel caso più generale, le funzioni libere hanno il vantaggio che non è necessario che siano definite nella stessa intestazione della classe, che può avere vantaggi. Ma questo chiaramente non è utilizzato qui.

Quindi, cosa dà? C'è una buona ragione per questo, o era solo una scelta sub-ottimale?

Edit: ho scritto un esempio veloce che dimostra due cose: sia che la conversione implicita funziona, se lo desideri con l'approccio amico, e che non gli errori sono causati duri se il tipo su modelli non soddisfa i requisiti del operatore di uguaglianza (ovviamente, assumendo che l'operatore di uguaglianza non sia usato in quel caso).Modifica: migliorato per contrastare con il primo approccio: http://coliru.stacked-crooked.com/a/6f8910945f4ed346.

+1

perché è stato downvoted? – Untitled123

+1

Sei sicuro che con 'friend'version,' assert (v1 == e); 'compilerebbe? – YSC

+0

@ YSC Sì, ho aggiunto un collegamento a Coliru, fammi sapere se risponde alla tua domanda completamente. –

risposta

0

EDIT: Dopo aver riletto la mia spiegazione e influenzato da alcuni commenti in giro, sono convinto che il mio ragionamento originale non sia davvero convincente. In pratica, la mia risposta ha tentato di sostenere che sebbene un valore x possa essere convertito implicitamente in un valore diverso da , un confronto di uguaglianza "automatico" tra i due potrebbe non essere necessariamente previsto. Per la contestualizzazione, sto ancora lasciando qui il codice che ho usato come esempio.

struct B {}; 

template <class T> 
struct A { 
    A() {} 
    A(B) {} 
    friend bool operator==(const A<T>&, const A<T>&) { return false; } 
}; 

// The template version wouldn't allow this to happen. 
// template <class T> 
// bool operator==(const A<T>&, const A<T>&) { return false; } 

int main() { 
    A<B> x; 
    B y; 
    if (x == y) {} //compiles fine 
    return 0; 
} 
+0

'A' afferma di essere implicitamente convertibile in da' B'. Una versione migliore ha 'B' che ha l'operatore' A', ma in entrambi i casi gli invarianti vengono violati per lo più significano che il codice di conversione è cattivo. Una preoccupazione più grande per me sarebbe il costo, ma anche le costose conversioni implicite sono anche odore di codice. – Yakk

+0

Assegnando A a un costruttore non esplicito di B, avete concordato che tutto ciò che accetta un A accetterà anche un B. Non c'è nulla di speciale nell'operatore di confronto qui. Inoltre, in termini di standard, A sta assumendo il ruolo di vettore, e il vettore non ha tali costruttori. –

+0

Il problema di visibilità è presente, ma può sempre fare la differenza se si chiama effettivamente con l'operatore di sintassi == (...). Quindi cambiando questo sarebbe indietro incompatibile. Ma nessuno vuole davvero farlo, quindi non spiega perché è così in primo luogo. –

2

Il techique si descrive (quello che io chiamo gli operatori Koenig) non era noto, almeno non ampiamente, al punto vector è stato progettato e in origine specificato.

Cambiarlo ora richiederebbe più attenzione rispetto al suo utilizzo originale e più giustificazione.

Come ipotesi, oggi gli operatori Koenig verrebbero utilizzati al posto degli operatori di template.

+0

"Come ipotesi, oggi gli operatori di Koenig verrebbero utilizzati al posto degli operatori di template." Ne dubito. 'basic_string_view' ha aggiunto una regola" sovraccarichi aggiuntivi sufficienti "per gestire le conversioni implicite. –

Problemi correlati