2015-10-25 8 views
12

Perché i seguenti due modelli non sono compatibili e non possono essere sovraccaricati?Perché un modello con tipo di reso dedotto non è sovraccaricabile con altre versioni di esso?

#include <vector> 

template<typename T> 
auto f(T t) { return t.size(); } 
template<typename T> 
auto f(T t) { return t.foobar(); } 

int main() { 
    f(std::vector<int>()); 
} 

penserei sono (più o meno) equivalente con la seguente, che compila bene (come non si può fare decltype auto(t.size()) non posso dare un equivalente esatto senza qualche rumore ..).

template<typename T> 
auto f(T t) -> decltype(t.size() /* plus some decay */) { return t.size(); } 

template<typename T> 
auto f(T t) -> decltype(t.foobar() /* plus some decay */) { return t.foobar(); } 

Clang e GCC lamentano main.cpp:6:16: error: redefinition of 'f' se lascio fuori il tipo di ritorno finale, tuttavia.

(Si noti che questo è un logica domanda io non sto cercando per il luogo nel standard che definisce questo comportamento -. Che è possibile includere nella vostra risposta anche, se lo si desidera - ma per una spiegazione del perché questo comportamento è desiderabile o status quo).

+0

non è possibile sovraccaricare un funzione sul suo tipo restituito, quindi hanno effettivamente lo stesso prototipo dal punto di vista del compilatore e per questo hai ottenuto l'errore * redefinition *. – skypjack

+0

@skypjack È possibile sovraccaricare i modelli di funzione sul tipo restituito. – jrok

+0

@skypjack Questo è del tutto irrilevante per i modelli di funzione, che è possibile sovraccaricare sul tipo restituito (fa parte della loro firma). – Columbo

risposta

6

Il dedotto tipo di ritorno può chiaramente non essere parte della firma. Tuttavia, l'inferenza di un'espressione che determina il tipo di reso (e partecipa a SFINAE) dalle istruzioni return presenta alcuni problemi. Diciamo che siamo stati a prendere la prima espressione return di dichiarazione e incollarlo in un po 'di regolazione, virtuale trailing-ritorno-tipo:

  1. Cosa succede se il restituito espressione dipende locali dichiarazioni? Questo non ci sta necessariamente fermando, ma ringhia tremendamente le regole. Non dimenticare che non possiamo usare i nomi delle entità dichiarate; Ciò potrebbe potenzialmente complicare il nostro livello di ritorno trailing-tipo per non avere alcun vantaggio.

  2. Un caso di utilizzo popolare di questa funzione sono i modelli di funzione che restituiscono lambda. Tuttavia, difficilmente possiamo fare una parte lambda della firma - le complicazioni che sarebbero sorte sono state elaborate in modo molto dettagliato prima. La sola mitragliatura richiederebbe sforzi eroici. Quindi dovremmo escludere i modelli di funzioni usando lambda.

  3. La firma di una dichiarazione non può essere determinata se non era anche una definizione, introducendo un'intera serie di altri problemi. La soluzione più semplice sarebbe quella di disabilitare completamente (non definendo) le dichiarazioni di tali modelli di funzioni, il che è quasi ridicolo.

Fortunatamente l'autore di N3386 si è sforzato di mantenere le regole (e l'implementazione!) Semplici. Non riesco a immaginare come non dover scrivere un trailing-return-type in alcuni casi angolari che giustifichi regole così meticolose.

+0

Bella risposta. Penso che accetterò. Una soluzione potrebbe essere quella di consentire a un modello di funzione (o funzione in generale) di assegnare un'espressione come risultato? 'template auto f (T t) = t.size();'. –

+0

Bene, possiamo facilmente * forzare * un lambda in una firma, usando 'decltype' o deduzione modello. – Deduplicator

+0

@Deduplicator Come? (Btw., Stiamo parlando del lambda (-expression), non del tipo di chiusura) – Columbo

1

Penso che potrebbe essere Comitato perdere ma retroscena a mio avviso è la seguente:

  1. Non si può sovraccaricare il tipo di funzione di ritorno. Ciò significa che in dichiarazione

    template<typename T> 
    auto f(T t) { return t.size(); } 
    

    Valore della auto non è interessante a compilatore infatti fino funzione di esemplificazione. Ovviamente compiller non aggiunge un certo controllo SFINAE al corpo funzione per controllare se T::size esiste in quanto non in tutti gli altri casi quando T viene utilizzato all'interno del corpo funzione

  2. Quando genera sovraccarichi compilatore verificherà se due firme funzione sono equivalenti esatti prendendo in considerazione tutte le possibili sostituzioni.

    Nel primo caso, allora compilatore otterrà smth come

    [template typename T] f(T) 
    [template typename T] f(T) 
    

    che sono esattamente equivalenti

    Nel secondo caso, tuttavia, come decltype specificato in modo esplicito verrà aggiunto agli argomenti template così avrai ottengono

    [template typename T, typename = typeof(T::size())] f(T) 
    [template typename T, typename = typeof(T::size())] f(T) 
    

    che non sono equivalenti esatti, ovviamente.

    Quindi il compilatore rifiuterà il primo caso mentre il secondo potrebbe essere OK quando si sostituisce il tipo reale anziché T.

1

Guardando i simboli creati dal mio compilatore:

[[email protected] ~]$ cat test1.cc 

#include <vector> 

template<typename T> 
auto JSchaubStackOverflow(T t) { return t.size(); } 

// template<typename T> 
// auto f(T t) { return t.foobar(); } 

int do_something() { 
     JSchaubStackOverflow(std::vector<int>()); 
     return 4; 
} 
[[email protected] ~]$ c++ -std=c++14 -pedantic test1.cc -c -o test1.o 
[[email protected] ~]$ nm test1.o | grep JScha 
0000000000000000 W _Z20JSchaubStackOverflowISt6vectorIiSaIiEEEDaT_ 
[[email protected] ~]$ nm -C test1.o | grep JScha 
0000000000000000 W auto JSchaubStackOverflow<std::vector<int, std::allocator<int> > >(std::vector<int, std::allocator<int> >) 
[[email protected] ~]$ cat test2.cc 

#include <vector> 

template<typename T> 
auto JSchaubStackOverflow(T t) -> decltype(t.size() /* plus some decay */) { return t.size(); } 

template<typename T> 
auto JSchaubStackOverflow(T t) -> decltype(t.foobar() /* plus some decay */) { return t.foobar(); } 
struct Metallica 
{ 

    Metallica* foobar() const 
    { 
     return nullptr; 
    } 
}; 


int do_something() { 
     JSchaubStackOverflow(std::vector<int>()); 
     JSchaubStackOverflow(Metallica()); 
     return 4; 
} 
[[email protected] ~]$ c++ -std=c++14 -pedantic test2.cc -c -o test2.o 
[[email protected] ~]$ nm test2.o | grep JScha 
0000000000000000 W _Z20JSchaubStackOverflowI9MetallicaEDTcldtfp_6foobarEET_ 
0000000000000000 W _Z20JSchaubStackOverflowISt6vectorIiSaIiEEEDTcldtfp_4sizeEET_ 
[[email protected] ~]$ nm -C test2.o | grep JScha 
0000000000000000 W decltype (({parm#1}.foobar)()) JSchaubStackOverflow<Metallica>(Metallica) 
0000000000000000 W decltype (({parm#1}.size)()) JSchaubStackOverflow<std::vector<int, std::allocator<int> > >(std::vector<int, std::allocator<int> >) 

Che cosa si può vedere da questo, è il decltype (qualunque sia) ci può aiutare a distinguere tra i simboli, è parte della firma. Ma "auto" non ci aiuta ...Quindi, se vettore aveva sia un foobar, e il metodo di dimensioni, entrambi i sovraccarichi di JSchaubStackOverflow sarebbero storpiato come Z20JSchaubStackOverflowISt6vectorIiSaIiEEEDaT Ora lascio a qualcun altro per trovare la sezione relativa a ISO sulle firme di funzioni template.

--EDIT-- So che ha già una risposta accettato, ma solo per la cronaca, ecco una difficoltà tecnica - dichiarazioni senza definizioni:

[[email protected] ~]$ cat test2.cc 

#include <vector> 

template<typename T> 
auto JSchaubStackOverflow(T t) -> decltype(t.size()); 

template<typename T> 
auto JSchaubStackOverflow(T t) -> decltype(t.foobar()); 

struct Metallica 
{ 

    Metallica* foobar() const 
    { 
     return nullptr; 
    } 
}; 


int do_something() { 
     JSchaubStackOverflow(std::vector<int>()); 
     JSchaubStackOverflow(Metallica()); 
     return 4; 
} 
[[email protected] ~]$ c++ -std=c++14 -pedantic test2.cc -c -o test2.o 
[[email protected] ~]$ nm -C test2.o | grep JScha 
       U decltype (({parm#1}.foobar)()) JSchaubStackOverflow<Metallica>(Metallica) 
       U decltype (({parm#1}.size)()) JSchaubStackOverflow<std::vector<int, std::allocator<int> > >(std::vector<int, std::allocator<int> >) 

Questo significa che si può fare tutto cosa senza corpi funzionali. Le specializzazioni del modello verrebbero fornite in un'altra unità di traduzione, ma per questo il linker deve trovarle ... quindi non è possibile sovraccaricare il corpo della funzione.

+0

Può il tuo manubrio 'nm' distrarre i simboli (ad esempio' nm -C')? Se tutto va bene, la differenza nei simboli è ancora evidente, ma è più umana. [edit: o se puoi alimentare l'output di 'nm' attraverso un' C++ filt' aggiornato che funziona anch'esso] –

+0

Quindi la logica è che i compilatori/linker non possono emettere simboli diversi per 'auto'? Potete per favore elaborare quale sia la difficoltà tecnica qui, per favore? –

+0

Beh, giusto, questo sembra ancora arbitrario, questo non è ancora un motivo, quindi non una risposta alla tua domanda ... –

1

"Solo i fallimenti nei tipi e le espressioni nel contesto immediato del tipo di funzione o suoi tipi di parametri del modello sono SFINAE errori.

Se la valutazione di un tipo/espressione sostituito provoca un effetto collaterale come l'istanziazione di alcuni template specialistici, la generazione di una funzione membro implicitamente definita, ecc. errori in questi effetti collaterali sono trattati come errori gravi. "source

La prima dichiarazione causa la sostituzione implicita del tipo restituito, e quindi non aderire a SFINAE