Ufficialmente, a cosa serve il nome di battesimo?


131

A volte ho visto alcuni messaggi di errore davvero indecifrabili gccemersi durante l'uso dei modelli ... In particolare, ho avuto problemi in cui dichiarazioni apparentemente corrette stavano causando errori di compilazione molto strani che magicamente sparivano prefissando iltypename parola chiave all'inizio del dichiarazione ... (Ad esempio, proprio la scorsa settimana, stavo dichiarando due iteratori come membri di un'altra classe di modelli e dovevo farlo) ...

Qual è la storia typename?


Risposte:


207

Di seguito è la citazione dal libro di Josuttis:

La parola chiave è typenamestata introdotta per specificare che l'identificatore che segue è un tipo. Considera il seguente esempio:

template <class T>
Class MyClass
{
  typename T::SubType * ptr;
  ...
};

Qui, typenameviene utilizzato per chiarire che SubTypeè un tipo di class T. Pertanto, ptrè un puntatore al tipo T::SubType. Senza typename, SubType sarebbe considerato un membro statico. così

T::SubType * ptr

sarebbe una moltiplicazione del valore SubTypedi tipo Tcon ptr.


2
Ottimo libro Leggilo una volta e poi tienilo come riferimento se vuoi.
deft_code

1
Il lettore astuto si renderà conto che un'espressione di moltiplicazione non è consentita dalla grammatica per una dichiarazione del membro. Come tale, C ++ 20 elimina la necessità di questo typename(sebbene non tutti!).
Davis Herring,

Non mi ha convinto. Una volta che il modello viene istanziato, è ben definito qual è il sottotipo T ::
kovarex

36

Il post BLOG di Stan Lippman suggerisce: -

Stroustrup ha riutilizzato la parola chiave di classe esistente per specificare un parametro di tipo anziché introdurre una nuova parola chiave che potrebbe ovviamente interrompere i programmi esistenti. Non è stata considerata una nuova parola chiave, ma solo che non è stata considerata necessaria data la sua potenziale interruzione. E fino allo standard ISO-C ++, questo era l'unico modo per dichiarare un parametro di tipo.

Fondamentalmente, quindi, Stroustrup ha riutilizzato la parola chiave class senza introdurre una nuova parola chiave che viene successivamente modificata nello standard per i seguenti motivi

Come nell'esempio fornito

template <class T>
class Demonstration {
public:
void method() {
    T::A *aObj; // oops …
     // …
};

la grammatica della lingua fraintende T::A *aObj;come espressione aritmetica, quindi viene introdotta una nuova parola chiave chiamatatypename

typename T::A* a6;

indica al compilatore di trattare la dichiarazione successiva come una dichiarazione.

Dal momento che la parola chiave era sul libro paga, diamine, perché non correggere la confusione causata dalla decisione originale di riutilizzare la parola chiave della classe.

Ecco perché abbiamo entrambi

Puoi dare un'occhiata a questo post , ti aiuterà sicuramente, ho appena estratto da esso il più possibile


Sì, ma allora perché era typenamenecessaria una nuova parola chiave , se potessi utilizzare la parola chiave esistente classper lo stesso scopo?
Jesper,

5
@Jesper: penso che la risposta di Xenus sia confusa qui. typenamedivenne necessario per risolvere il problema di analisi come descritto nella risposta di Naveen citando Josuttis. (Non credo che l'inserimento di a classin questo posto avrebbe funzionato.) Solo dopo che la nuova parola chiave è stata accettata per questo caso, è stata anche consentita nelle dichiarazioni di argomenti del modello ( o è quella definizione? ), Perché classci sono sempre state ingannevole.
sbi,

13

Considera il codice

template<class T> somefunction( T * arg )
{
    T::sometype x; // broken
    .
    .

Sfortunatamente, il compilatore non deve essere sensitivo e non sa se T :: qualcosa finirà per riferirsi a un nome di tipo o a un membro statico di T. Quindi, si usa typenameper dirlo:

template<class T> somefunction( T * arg )
{
    typename T::sometype x; // works!
    .
    .

6

In alcune situazioni in cui ti riferisci a un membro del cosiddetto dipendente tipo (che significa "dipendente dal parametro modello"), il compilatore non può sempre dedurre in modo univoco il significato semantico del costrutto risultante, perché non sa che tipo di nome sia (ovvero se si tratta di un nome di un tipo, un nome di un membro di dati o un nome di qualcos'altro). In casi del genere devi chiarire la situazione dicendo esplicitamente al compilatore che il nome appartiene a un nome di tipo definito come membro di quel tipo dipendente.

Per esempio

template <class T> struct S {
  typename T::type i;
};

In questo esempio la parola chiave è typenamenecessaria per la compilazione del codice.

La stessa cosa accade quando si desidera fare riferimento a un membro modello di tipo dipendente, ovvero a un nome che designa un modello. Devi anche aiutare il compilatore usando la parola chiave template, sebbene sia posizionata diversamente

template <class T> struct S {
  T::template ptr<int> p;
};

In alcuni casi potrebbe essere necessario utilizzare entrambi

template <class T> struct S {
  typename T::template ptr<int>::type i;
};

(se ho ottenuto la sintassi correttamente).

Naturalmente, un altro ruolo della parola chiave typenamedeve essere utilizzato nelle dichiarazioni dei parametri del modello.


Vedi anche una descrizione della parola chiave typename C ++ per ulteriori informazioni (di base).
Atafar,

5

Il segreto sta nel fatto che un modello può essere specializzato per alcuni tipi. Ciò significa che può anche definire l'interfaccia completamente diversa per diversi tipi. Ad esempio puoi scrivere:

template<typename T>
struct test {
    typedef T* ptr;
};

template<>         // complete specialization 
struct test<int> { // for the case T is int
    T* ptr;
};

Ci si potrebbe chiedere perché sia ​​utile e davvero: sembra davvero inutile. Ma tieni presente che ad esempio std::vector<bool>il referencetipo sembra completamente diverso rispetto ad altri T. Certo, non cambia il tipo di referenceda un tipo a qualcosa di diverso, ma potrebbe succedere.

Ora cosa succede se scrivi i tuoi modelli usando questo testmodello. Qualcosa come questo

template<typename T>
void print(T& x) {
    test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

sembra che vada bene per te perché ti aspetti che test<T>::ptrsia un tipo. Ma il compilatore non lo sa e in realtà viene persino consigliato dallo standard di aspettarsi il contrario, test<T>::ptrnon è un tipo. Per dire al compilatore cosa ti aspetti devi aggiungere un typenameprima. Il modello corretto è simile al seguente

template<typename T>
void print(T& x) {
    typename test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

In conclusione: devi aggiungere typenameprima ogni volta che usi un tipo nidificato di un modello nei tuoi modelli. (Naturalmente solo se per quel modello interno viene utilizzato un parametro modello del modello.)


5

Due usi:

  1. Come templateparola chiave argomento (anziché class)
  2. Una typenameparola chiave indica al compilatore che un identificatore è un tipo (anziché una variabile membro statica)
template <typename T> class X  // [1]
{
    typename T::Y _member;  // [2] 
}

4

Penso che tutte le risposte abbiano indicato che la typenameparola chiave viene utilizzata in due casi diversi:

a) Quando si dichiara un parametro del tipo di modello. per esempio

template<class T> class MyClass{};        // these two cases are
template<typename T> class MyNewClass{};  // exactly the same.

Che non c'è differenza tra loro e sono ESATTAMENTE gli stessi.

b) Prima di utilizzare un nome di tipo dipendente nidificato per un modello.

template<class T>
void foo(const T & param)
{
   typename T::NestedType * value; // we should use typename here
}

Quale non utilizzo typenameporta a errori di analisi / compilazione.

Quello che voglio aggiungere al secondo caso, come menzionato nel libro di Scot Meyers Effective C ++ , è che esiste un'eccezione nell'uso typenameprima di un nome di tipo dipendente annidato . L'eccezione è che se si utilizza il file nome del tipo dipendente nidificato come classe di base o in un elenco di inizializzazione membro , non è necessario utilizzaretypenamelì:

template<class T>
class D : public B<T>::NestedType               // No need for typename here
{
public:
   D(std::string str) : B<T>::NestedType(str)   // No need for typename here
   {
      typename B<T>::AnotherNestedType * x;     // typename is needed here
   }
}

Nota: l' utilizzo typenameper il secondo caso (ovvero prima del nome di tipo dipendente nidificato) non è necessario dal C ++ 20.


2
#include <iostream>

class A {
public:
    typedef int my_t;
};

template <class T>
class B {
public:
    // T::my_t *ptr; // It will produce compilation error
    typename T::my_t *ptr; // It will output 5
};

int main() {
    B<A> b;
    int my_int = 5;
    b.ptr = &my_int;
    std::cout << *b.ptr;
    std::cin.ignore();
    return 0;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.