2015-01-30 5 views
50

Diciamo che sto scrivendo una funzione per stampare la lunghezza di una stringa:Perché il decadimento del puntatore ha la precedenza su un modello dedotto?

template <size_t N> 
void foo(const char (&s)[N]) { 
    std::cout << "array, size=" << N-1 << std::endl; 
} 

foo("hello") // prints array, size=5 

Ora voglio estendere foo per sostenere non -arrays:

void foo(const char* s) { 
    std::cout << "raw, size=" << strlen(s) << std::endl; 
} 

ma si scopre che questo spezza il originale destinazione d'uso:

foo("hello") // now prints raw, size=5 

Perché? Non richiederebbe una conversione da matrice a puntatore, mentre il modello sarebbe una corrispondenza esatta? C'è un modo per garantire che venga richiamata la funzione dell'array?

+1

correlati: http://stackoverflow.com/questions/5347444/overload-resolution-and-arrays-which-function-should-be-called – 0x499602D2

+3

'foo <> ("ciao");' chiamerà il modello se aiuta Penso che sia un modo semplice per rifiutare il modello. E per forzare il non-template usa '(& pippo) (" ciao ")' - puoi prendere l'indirizzo di un modello diverso da modello, ma non di un modello. –

risposta

42

La ragione fondamentale di questa ambiguità (conforme alle norme) sembra essere all'interno del costo di conversione: la risoluzione di sovraccarico tenta di ridurre al minimo le operazioni eseguite per convertire un argomento nel parametro corrispondente. Un array è efficacemente il puntatore al suo primo elemento, tuttavia, decorato con alcune informazioni sul tipo in fase di compilazione. Una conversione da matrice a puntatore non costa più di ad es. salvare l'indirizzo della matrice stessa o inizializzarne un riferimento. Da quella prospettiva, l'ambiguità sembra giustificata, sebbene concettualmente non sia intuitivo (e potrebbe essere scadente). In effetti, questa argomentazione si applica a tutte le trasformazioni di Lvalue, come suggerito dalla seguente citazione. Un altro esempio:

void g() {} 

void f(void(*)()) {} 
void f(void(&)()) {} 

int main() { 
    f(g); // Ambiguous 
} 

Il seguente è standardese obbligatoria. Le funzioni che non sono specializzazioni di alcuni template funzione sono preferite rispetto a quelle che sono se entrambe sono ugualmente una corrispondenza altrettanto buona (si veda [over.match.best]/(1.3), (1.6)). Nel nostro caso, la conversione eseguita è una conversione da matrice a puntatore, che è una trasformazione Lvalue con rank di corrispondenza esatta (in base alla tabella 12 in [over.ics.user]). [Over.ics.rank]/3:

  • sequenza di conversione standard S1 è una migliore sequenza di conversione di sequenza di conversione standard S2 se

    • S1 è una vera e propria sottosequenza di S2 (a confronto le sequenze di conversione nella forma canonica definita da 13.3.3.1.1, esclusa qualsiasi Lvalue Transformation; la sequenza di conversione dell'identità è considerata ered essere una sottosequenza di qualsiasi conversione sequenza non-identità) o, se non che,

    • la posizione di S1 è meglio che la posizione di S2 o S1 e S2 assimilato e sono distinguibili dalla regole nel paragrafo successivo, o, se non che,

    • [..]

Il primo punto elenco esclude la nostra conversione (poiché è una trasformazione Lvalue).Il secondo richiede una differenza nei ranghi, che non è presente, in quanto entrambe le conversioni hanno un match rank esatto; Le "regole nel paragrafo seguente", cioè in [over.ics.rank]/4, non coprono neanche le conversioni da matrice a puntatore.
Quindi credici o no, nessuna delle due sequenze di conversione è migliore dell'altra, e quindi viene selezionato il -overload char const*.


soluzione possibile: Definire il secondo overload come modello funzione così, allora calci ordinamento parziale e seleziona il primo.

template <typename T> 
auto foo(T s) 
    -> std::enable_if_t<std::is_convertible<T, char const*>{}> 
{ 
    std::cout << "raw, size=" << std::strlen(s) << std::endl; 
} 

Demo.

+0

Immaginate una funzione 'bar' che accetta un oggetto di tipo' X'. Inoltre, 'X' ha un solo costruttore che accetta come argomento un' const char * '. Puoi chiamare 'bar (" ciao ");' anche se sembra che tu stia ottenendo due conversioni, da matrice a puntatore e da puntatore a X, quando normalmente è consentita una sola conversione implicita. Immagino che otteniamo il decadimento array-to-pointer "gratuitamente". È un modo accettabile per dirlo? –

+2

@AaronMcDaid Nope. La conversione da matrice a puntatore è una sequenza di conversione standard, mentre il costruttore è una sequenza di conversione definita dall'utente. Quest'ultimo è permesso solo una volta, ma il primo può anche verificarsi in combinazione con esso. :) – Columbo

+0

* il costruttore di conversioni richiamato fa parte di una sequenza di conversione definita dall'utente. Una sequenza di conversione definita dall'utente consiste in una conversione definita dall'utente (chiamata ctor) più una sequenza di copione standard iniziale (da matrice a puntatore) più una sequenza di conversione standard finale (qui, identità). Ovviamente non è la sequenza stessa. – Columbo