Oficialmente, para que serve o typename?

131

Na ocasião, vi algumas mensagens de erro realmente indecifráveis ​​serem exibidas gccao usar modelos ... Especificamente, tive problemas em que declarações aparentemente corretas estavam causando erros de compilação muito estranhos que desapareceram magicamente, prefixando a typenamepalavra-chave no início do declaração ... (Por exemplo, na semana passada, eu estava declarando dois iteradores como membros de outra classe de modelo e tive que fazer isso) ...

Qual é a história typename?

dicroce
fonte

Respostas:

207

A seguir, a citação do livro de Josuttis:

A palavra-chave typenamefoi introduzida para especificar que o identificador a seguir é um tipo. Considere o seguinte exemplo:

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

Aqui, typenameé usado para esclarecer que SubTypeé um tipo de class T. Assim, ptré um ponteiro para o tipo T::SubType. Sem typename, SubType seria considerado um membro estático. portanto

T::SubType * ptr

seria uma multiplicação do valor SubTypedo tipo Tcom ptr.

Naveen
fonte
2
Grande livro. Leia-o uma vez e mantenha-o como referência, se quiser.
Deft_code 21/10/09
1
O leitor astuto perceberá que uma expressão de multiplicação não é permitida pela gramática para uma declaração de membro. Como tal, o C ++ 20 dispensa a necessidade disso typename(embora nem todos eles!).
Davis Herring
Não me convenceu. Uma vez que o modelo está sendo instanciado, ele é muito bem definido o que é o T :: subtipo
kovarex
36

O post no blog de Stan Lippman sugere: -

O Stroustrup reutilizou a palavra - chave da classe existente para especificar um parâmetro de tipo, em vez de introduzir uma nova palavra-chave que poderia, é claro, interromper os programas existentes. Não que uma nova palavra-chave não tenha sido considerada - apenas que ela não foi considerada necessária, devido à possível interrupção. E até o padrão ISO-C ++, essa era a única maneira de declarar um parâmetro de tipo.

Então, basicamente, o Stroustrup reutilizou a palavra-chave da classe sem introduzir uma nova palavra-chave que é alterada posteriormente no padrão pelos seguintes motivos

Como o exemplo dado

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

a gramática da linguagem interpreta mal T::A *aObj;como uma expressão aritmética, para que uma nova palavra-chave seja introduzida chamadatypename

typename T::A* a6;

instrui o compilador para tratar a instrução subseqüente como uma declaração.

Como a palavra-chave estava na folha de pagamento, heck, por que não corrigir a confusão causada pela decisão original de reutilizar a palavra-chave da classe.

É por isso que temos ambos

Você pode dar uma olhada neste post , ele definitivamente o ajudará, acabei de extrair o máximo que pude

Xinus
fonte
Sim, mas por que uma nova palavra-chave era typenamenecessária, se você poderia usar a palavra-chave existente classpara a mesma finalidade?
Jesper
5
@ Jesper: Acho que a resposta do Xenus é confusa aqui. typenametornou-se necessário corrigir o problema de análise, conforme descrito na resposta de Naveen, citando Josuttis. (Não acho que a inserção de um classneste local funcionaria.) Somente depois que a nova palavra-chave foi aceita para esse caso, ela também foi permitida nas declarações de argumento do modelo ( ou são essas definições? ), Porque classsempre houve alguma enganoso.
sbi 23/10/09
13

Considere o código

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

Infelizmente, não é necessário que o compilador seja psíquico e não sabe se T :: sometype acabará se referindo a um nome de tipo ou a um membro estático de T. Portanto, é usado typenamepara dizer:

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

Em algumas situações em que você se refere a um membro do chamado tipo dependente (significando "dependente do parâmetro do modelo"), o compilador nem sempre pode deduzir inequivocamente o significado semântico da construção resultante, porque não sabe que tipo de nome é (ou seja, se é o nome de um tipo, o nome de um membro de dados ou o nome de outra coisa). Em casos como esse, é necessário desambiguar a situação informando explicitamente ao compilador que o nome pertence a um nome de tipo definido como um membro desse tipo dependente.

Por exemplo

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

Neste exemplo, a palavra-chave é typenamenecessária para a compilação do código.

O mesmo acontece quando você deseja se referir a um membro do modelo do tipo dependente, ou seja, a um nome que designa um modelo. Você também precisa ajudar o compilador usando a palavra-chave template, embora ela seja colocada de maneira diferente

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

Em alguns casos, pode ser necessário usar os dois

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

(se obtive a sintaxe corretamente).

Obviamente, outra função da palavra typename- chave deve ser usada nas declarações de parâmetros do modelo.

Formiga
fonte
Consulte também A Descrição da palavra-chave typename C ++ para obter mais informações (em segundo plano).
Atafar 5/06/15
5

O segredo está no fato de que um modelo pode ser especializado para alguns tipos. Isso significa que também pode definir a interface completamente diferente para vários tipos. Por exemplo, você pode escrever:

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

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

Alguém pode perguntar por que isso é útil e de fato: Isso realmente parece inútil. Mas lembre-se de que, por exemplo, std::vector<bool>o referencetipo parece completamente diferente do que para outros Ts. É certo que não muda o tipo de referenceum tipo para algo diferente, mas, no entanto, pode acontecer.

Agora, o que acontece se você escrever seus próprios modelos usando esse testmodelo. Algo assim

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

parece estar bem para você, porque você espera que esse test<T>::ptrseja um tipo. Mas o compilador não sabe e, de fato, ele é aconselhado pelo padrão a esperar o contrário, test<T>::ptrnão é um tipo. Para dizer ao compilador o que você espera, adicione um typenameantes. O modelo correto se parece com isso

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

Conclusão: você deve adicionar typenameantes sempre que usar um tipo aninhado de modelo nos seus modelos. (Obviamente, apenas se um parâmetro do modelo for usado para esse modelo interno.)

phlipsy
fonte
5

Dois usos:

  1. Como uma templatepalavra-chave de argumento (em vez de class)
  2. Uma typenamepalavra-chave informa ao compilador que um identificador é um tipo (em vez de uma variável de membro estática)
template <typename T> class X  // [1]
{
    typename T::Y _member;  // [2] 
}
Phillip Ngan
fonte
4

Acho que todas as respostas mencionaram que a typenamepalavra - chave é usada em dois casos diferentes:

a) Ao declarar um parâmetro do tipo de modelo. por exemplo

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

Que não há diferença entre eles e eles são exatamente os mesmos.

b) Antes de usar um nome de tipo dependente aninhado para um modelo.

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

Que não usar typenameleva a erros de análise / compilação.

O que quero acrescentar ao segundo caso, conforme mencionado no livro Effective C ++ , de Scot Meyers , é que há uma exceção do uso typenameantes de um nome de tipo dependente aninhado . A exceção é que, se você usar o nome do tipo dependente aninhado como uma classe base ou em uma lista de inicialização de membros , não deverá usar typenamelá:

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: O uso typenamepara o segundo caso (ou seja, antes do nome do tipo dependente aninhado) não é necessário desde o C ++ 20.

Gupta
fonte
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;
}
Jobin
fonte