2012-12-31 12 views
5

L'uscita del seguente programma ...Costruttore modello che ha la precedenza su Costruttore copia e sposta normale?

#include <iostream> 

using namespace std; 

struct X 
{ 
    X(const X&)    { cout << "copy" << endl; } 
    X(X&&)     { cout << "move" << endl; } 

    template<class T> X(T&&) { cout << "tmpl" << endl; } 
}; 

int main() 
{ 
    X x1 = 42; 
    X x2(x1); 
} 

è

tmpl 
tmpl 

L'output desiderato è:

tmpl 
copy 

Perché non il costruttore di copia concreta prendono la precedenza sul costruttore di modelli?

C'è comunque un modo per risolvere il problema in modo che i sovraccarichi del costruttore di copia e spostamento abbiano la precedenza sul costruttore di template?

+0

Ecco la classe finale relativo a questa domanda: http://codereview.stackexchange.com/questions/20058/a-c11-any-class –

risposta

2

Normale regole di risoluzione di sovraccarico si applicano ancora quando si sceglie il costruttore - e un costruttore che prende un riferimento a valore non costante (per il costruttore di template dopo la deduzione dell'argomento) è una corrispondenza migliore di un costruttore che prende un riferimento di constval.

Si potrebbe, ovviamente, aggiungere un altro sovraccarico prendendo un riferimento a valore non costante, ad es.

X(X&)    { cout << "copy" << endl; } 

Aggiornamento: altri casi in cui il costruttore modello è una migliore corrispondenza:

const X f() 
{ return X(); } 

struct Y : X 
{ Y() { } }; 

int main() 
{ 
    X x3(f()); // const-qualified rvalue 
    Y y; 
    X x4(y); // derived class 
} 
+0

Ho anche bisogno di aggiungere 'X (const X &&)' o è questo impossibile? Ci sono altri casi in cui il costruttore di mosse o copia potrebbe coincidere con l'abscence del costruttore di template? –

+0

@AndrewTomazosFathomlingCorps: SFINAE può aiutare lì, in modo che si può avere sia la funzione di firma * * intatto (penso che è importante). Ad esempio, è possibile utilizzare 'std :: enable_if' nel modello di funzione per abilitare solo i casi desiderati. – Nawaz

+0

Sì, se vi capita di avere un rvalue const, per esempio da 'const X f();' e 'X x3 (f())'. Poi c'è il caso in cui l'inizializzatore è una classe derivata che usa anche il costruttore di template. – cmeerw

5

Bene, è a causa di reference-collapsing.

Nella fase risoluzione di sovraccarico, quando il modello di funzione viene istanziata, T è deduded essere X&, così T&& (che è X& &&) diventa X& causa riferimento-collasso, e la funzione un'istanza dal modello di funzione diventa esatta corrisponde a e il copy-constructor richiede una conversione da X& a const X& (motivo per cui non è selezionato corrispondenza inferiore).

Tuttavia, se si rimuove il const dal costruttore di copie, verrà preferito il costruttore di copia. Prova questo:

X(/*const*/ X&) { cout << "copy" << endl; } 

Output come previsto.

Oppure, in alternativa, se si effettua il parametro nel modello funzione const T& allora costruttore di copia sarà chiamato (anche se rimane lo stesso!), Perché di riferimento-collasso non entrerà in immagine ora:

template<class T> X(const T &) { cout << "tmpl" << endl; } 

Output è previsto, di nuovo.

3

Se non si desidera aggiungere un altro costruttore (come altre risposte hanno suggerito), è possibile utilizzare SFINAE per vincolare la chiamata, sostituendo il vostro costruttore di modello da questo:

template<class T 
    , typename std::enable_if<not std::is_same<X, typename std::decay<T>::type>::value, int>::type = 0 
> X(T&&) { cout << "tmpl " << endl; } 

il che comporta l'aggiunta di un templ dummy di default argomento trattato (tecnica nota: http://www.boost.org/doc/libs/1_52_0/libs/utility/enable_if.html). Non sono necessarie intestazioni aggiuntive.

otterrete il risultato desiderato.

ho avuto questa risposta da un problema correlato: http://flamingdangerzone.com/cxx11/2012/06/05/is_related.html. Tutto questo sembra piuttosto inelegante, ma sembra essere l'unica via d'uscita per ora. Mi piacerebbe ancora vedere una soluzione più elegante.

Problemi correlati