2015-05-05 14 views
13

Itanium ABI specifies che, con un paio di eccezioni non interessanti, il tipo di ritorno è incluso nei nomi storti degli instantions di template ma non in quelli non-template.Perché il tipo di ritorno delle istanze del modello di funzione C++ è incluso nel nome della funzione storta?

Perché è questo? In tal caso, potresti avere due istanze di modelli di funzione in cui il linker deve distinguerli perché non è indicativo di una violazione di una regola di definizione o simili?

Come esempio di ciò che intendo:

class ReturnType {}; 
class ParamType {}; 

template <typename T> 
ReturnType foo(T p) { 
    return ReturnType(); 
}; 
template ReturnType foo<ParamType>(ParamType); 

ReturnType bar(ParamType p) { 
    return ReturnType(); 
} 

Poi il file oggetto risultante ha manglings:

ReturnType foo<ParamType>(ParamType) 
    => _Z3fooI9ParamTypeE10ReturnTypeT_ 
         ^^^^^^^^^^^^ 

ReturnType bar(ParamType) 
    => _Z3bar9ParamType 

Perché foo necessità ReturnType maciullata ma bar non lo fa?

(sto presumendo c'è una ragione e non è solo una scelta arbitraria.)

risposta

15

Forse perché, a differenza di normali funzioni, una firma modelli funzione contiene il tipo di ritorno? §1.3:

1.3.17 firma< funzione > nome, il tipo di parametro elenco (8.3.5), e namespace allegando (se presente)
[Nota: firme vengono utilizzati come base per nome mangling e linking.-nota end]


1.3.18 firma < funzione nome del modello >, tipo di parametro elenco (8.3.5), allegando namespace (se presente), ritorno tipo, e il parametro modello di elenco

si consideri che possiamo avere due completamente distinte sovraccarichi modello di funzione che differiscono solo nel loro tipo di ritorno, se scritto così:

template <int> 
char foo(); 

template <int> 
int foo(); 

Se la manomissione dei nomi non considera il tipo di reso, il collegamento di tali modelli risulterebbe difficile, dal momento che foo<0> non specifica univocamente una specializzazione. Eppure, una specializzazione può essere affrontato utilizzando la risoluzione di sovraccarico (senza argomenti):

int (*funptr)() = foo<0>; 

D'altra parte, tra cui il tipo di ritorno non è necessaria per le funzioni ordinarie, in quanto questi non possono essere sovraccaricati al loro tipo di ritorno - vale a dire la loro la firma non include il tipo di reso.

+0

@ dyp Ho provato, come è ora? – Columbo

+0

Davvero un bell'esempio. Stavo anche pensando: l'ODR potrebbe essere violato se il tipo di reso non fosse parte della firma? Per esempio. se si ha il primo modello in TU 0 e il secondo in TU 1. – dyp

+0

@dyp Sì, IIRC le dichiarazioni devono essere costituite dalla stessa sequenza di token (o una sequenza equivalente di token, per alcune definizioni di equivalenti), se si verificano alla stessa entità – Columbo

8

Le funzioni del modello possono essere sovraccaricate dal solo tipo di ritorno, a differenza delle normali funzioni.

template <typename T> int f() { return 1; } 
template <typename T> long f() { return 2; } 

int main() { 
    int (&f1)() = f<void>; 
    long (&f2)() = f<void>; 
    return f1() == f2(); 
} 

Qui, ipotizzando un compilatore non ottimizzare, il complesso generato conterrà due funzioni f<void>(), ma non possono condividere lo stesso nome alterato, o non ci sarebbe modo per il montaggio generato per main per specificare quale delle istanze cui si riferisce.

In genere, se si dispone di una funzione modello sovraccarico, solo una delle definizioni verrà utilizzata per un argomento modello specifico, quindi questo non è comune, ma nei commenti sulla risposta di Columbo, dyp ha trovato l'idea di base su come questo potrebbe effettivamente essere utile. In Can addressof() be implemented as constexpr function?, mi si avvicinò con

template <bool> 
struct addressof_impl; 

template <> 
struct addressof_impl<false> { 
    template <typename T> 
    static constexpr T *impl(T &t) { 
    return &t; 
    } 
}; 

template <> 
struct addressof_impl<true> { 
    template <typename T> 
    static /* not constexpr */ T *impl(T &t) { 
    return reinterpret_cast<T *>(&const_cast<char &>(reinterpret_cast<const volatile char &>(t))); 
    } 
}; 

template <typename T> 
constexpr T *addressof(T &t) 
{ 
    return addressof_impl<has_overloaded_addressof_operator<T>::value>::template impl<T>(t); 
} 

ma questo è in realtà una violazione di ODR se lo stesso esemplificazione addressof<X> viene utilizzato in molteplici unità di traduzione, alcuni dove X è incompleto, e alcuni dove X è completo e dispone di un sovraccarico & operatore. Questo può essere rielaborato eseguendo direttamente la logica all'interno di addressof, usando normali funzioni sovraccariche.

template <typename T> 
std::enable_if_t<has_overloaded_addressof_operator<T>::value, T *> 
addressof(T &t) 
{ 
    return reinterpret_cast<T *>(&const_cast<char &>(reinterpret_cast<const volatile char &>(t))); 
} 

template <typename T> 
constexpr 
std::enable_if_t<!has_overloaded_addressof_operator<T>::value, T *> 
addressof(T &t) 
{ 
    return &t; 
} 

(. has_overloaded_addressof_operator avrebbe bisogno di essere inline troppo, per lo stesso motivo)

In questo modo, il problema viene evitato: quando X è incompleta, quindi addressof<X> si riferisce ad una funzione diversa rispetto a quando X è completare.

+0

Ma con le tue modifiche, un id-template con 'addressof' fa sempre riferimento ad una specializzazione in modo univoco, quindi questo non richiede un mangling dei tipi di ritorno. – Columbo

+0

@Columbo 'indirizzo di ' potrebbe fare riferimento alla prima definizione in un'unità di traduzione, ma alla seconda definizione in un'altra unità di traduzione, per la stessa 'X'.Quando queste unità di traduzione possono essere collegate insieme in un singolo programma, i loro nomi storti devono essere diversi. – hvd

+0

@Columbo Non vedo come, puoi elaborare? 14.6.4.2 riguarda cosa succede se ci sono altre definizioni di 'addressof' in altre unità di traduzione, ma non ci sono altre definizioni di' addressof' in altre unità di traduzione. – hvd

Problemi correlati