Conversione di tipo implicita in C ++ con modello

Ho un modello di class A

 template  class A { public: A(int) {} }; 

Quale ha un costruttore da int . E ho un’operazione:

 template A operator+(const A&, const A&) { retrun A(0); } 

Ma quando chiamo:

 A a(4); A b = a + 5; A c = 5 + a; 

Vorrei che int venisse convertito implicitamente in A, ma i compilatori generano errori.

C’è un modo elegante per abilitare la conversione implicita senza utilizzare tali soluzioni come:

  • a + A(5)
  • operator+(a, 5)

La soluzione è già mostrata in questa risposta . Ora, di più sul problema …

Il problema nel codice è come viene eseguita la risoluzione di sovraccarico. Quando una funzione template viene considerata per la risoluzione di sovraccarico, il compilatore eseguirà la deduzione di tipo sugli argomenti e verrà visualizzata una sostituzione di tipo che corrisponde alla chiamata oppure non riesce ad applicare quel modello, lo rimuove dall’insieme di potenziali candidati e continua oltre. Il problema a questo punto è che la deduzione di tipo deduce solo corrispondenze esatte (con una qualifica extra const / volatile). Poiché la corrispondenza è esatta, il compilatore non utilizzerà alcuna conversione (di nuovo, diversa da cv).

L’esempio più semplice di questo avviene con le funzioni std::max e std::min :

 unsigned int i = 0; std::min( i, 10 ); // Error! 

La deduzione di tipo dedurrà la T nel template min( T const &, T const & ) da unsigned per il primo argomento ma int per il secondo differiscono e il compilatore scarterà questa funzione template.

La soluzione proposta nella risposta è l’utilizzo di una funzionalità del linguaggio che consente di definire una funzione di amico non membro all’interno della definizione della class. Il vantaggio con i template è che per ogni (diversa) istanza del template, il compilatore creerà una funzione non-template libera a livello di spazio dei nomi che ha la firma ottenuta sostituendo i veri tipi di istanza nella dichiarazione friend:

 template  class test { friend test operator+( test const & lhs, test const & rhs ) { // [1] return test(); } } test t; // [2] 

Nell’esempio sopra, il compilatore consente di aggiungere la definizione della funzione friend all’interno dell’ambito class in [1]. Quindi, quando istanziate il modello in [2], il compilatore genererà una funzione libera:

 test operator+( test const & lhs, test const & rhs ) { return test(); } 

La funzione è sempre definita, sia che la si usi o meno (questo differisce dalle funzioni dei membri della class template, che vengono istanziate su richiesta).

La magia qui ha più lati. La prima parte è che generalmente si definiscono funzioni non template per tutti e tutti i tipi di istanze, così si ottiene genericità e allo stesso tempo il vantaggio della risoluzione di sovraccarico che può utilizzare questa funzione quando gli argomenti non sono corrispondenze perfette .

Poiché è una funzione non modello, il compilatore è in grado di chiamare conversioni implicite su entrambi gli argomenti e otterrai il comportamento previsto.

Inoltre, un diverso tipo di magia procede con la ricerca, poiché la funzione così definita può essere trovata solo dalla ricerca dipendente dall’argomento, a meno che non sia dichiarata anche a livello di spazio dei nomi, cosa che nel nostro caso non può essere fatta in modo generico. L’implicazione di questo potrebbe essere buono o cattivo, a seconda di come lo si vuole considerare …

Poiché può essere trovato solo da ADL, non verrà preso in considerazione a meno che almeno uno degli argomenti sia già del tipo desiderato (ovvero non verrà mai utilizzato per eseguire conversioni su entrambi gli argomenti). Lo svantaggio è che è imansible fare riferimento alla funzione a meno che non lo si stia effettivamente chiamando e ciò significa che non è ansible ottenere un puntatore a funzione.

(Maggiori informazioni sul modello di amicizia qui , ma si noti che in questo caso particolare, tutte le altre varianti non riusciranno a eseguire conversioni implicite).

Ogni tentativo di fornire un operatore utilizzando i modelli richiederà almeno un secondo sovraccarico. Ma puoi evitarlo definendo l’operatore all’interno della class:

 template  class A { public: A(int) {} inline friend A operator+(const A& a, const A& b) { return A(0); } }; 

Funziona per entrambi, a+5 e 5+a .

Aggiungi questo operatore

 template A operator+(const A&, const int&) { return A(0); } 

O prova questo

 template  class A { friend const A operator+(const A& a, const A& b) { return A(0); } public: A(int) {} // OR FOR UNARY // const A operator+(const A &a) const {return A(0);} }; int main(){ A<3> a(4); A<3> b = a + 5; A<3> c = 5 + a; 

}

Puoi provare ad aggiungere un ulteriore argomento di tipo “politica” al modello per la tua class A che determinerà il tipo di conversione effettivo desiderato. Per esempio:

 template  class A { public: typedef ConvVal conv_val; A(ConvVal) {} }; template 

Ora la tua class A prenderà un argomento di modello aggiuntivo che assume come valore predefinito un tipo int e definisce il tipo effettivo da cui verranno consentite le conversioni automatiche. Devi solo sovraccaricare la funzione operator+ tre volte, una volta per la versione senza il valore di conversione che prenderà classi esplicite di tipo A , e un’altra per le due versioni di operator+ che prenderà i tipi di conversione. Nel codice precedente ho generalizzato questo con più tipi generici in modo che questo possa essere fatto praticamente con qualsiasi altra class che abbia la firma del modello corretta e definisca un conv_val .