Detalhes de instanciação de modelos de compiladores GCC e MS

Alguém poderia fornecer uma comparação ou detalhes específicos de como a instanciação de modelo é tratada na compilation e / ou tempo de link nos compiladores GCC e MS? Este processo é diferente no contexto de bibliotecas estáticas, bibliotecas compartilhadas e executáveis? Eu encontrei este documento sobre como o GCC lida com isso, mas não tenho certeza se as informações ainda estão se referindo ao estado atual das coisas. Devo usar as bandeiras que eles sugerem ao compilar minhas bibliotecas, por exemplo, -fno-implicit-templates ?

O que eu sei (pode não ser necessariamente correto) é que:

  • modelos serão instanciados quando realmente utilizados
  • modelos serão instanciados como resultado de instanciações explícitas
  • instanciação duplicada é normalmente tratada duplicando instanciações duplicadas ou adiando a instanciação até o tempo de link


Ponto de instanciação

modelos serão instanciados quando realmente utilizados

Não exatamente, mas grosseiramente. O ponto preciso de instanciação é um pouco sutil, e eu o deloco para a seção chamada Ponto de instanciação no belo livro de Vandevoorde / Josuttis.

No entanto, compiladores não necessariamente implementam os POIs corretamente: Bug c ++ / 41995: Ponto incorreto de instanciação para o modelo de function


Instanciação parcial

modelos serão instanciados quando realmente utilizados

Isso está parcialmente correto. Isso é verdadeiro para modelos de function, mas para modelos de classs, apenas as funções de membro usadas são instanciadas. O seguinte é um código bem formado:

#include  template  struct Foo { void let_me_stay() { this->is->valid->code. get->off->my->lawn; } void fun() { std::cout << "fun()" << std::endl; } }; int main () { Foo foo; foo.fun(); } 

let_me_stay() é verificado sintaticamente (e a syntax está correta), mas não semanticamente (isto é, não é interpretada).

Pesquisa em duas fases

No entanto, apenas o código dependente é interpretado posteriormente; Claramente, dentro de Foo<> , this depende do id-template exato com o qual Foo<> é instanciado, então nós adiamos a verificação de erros de Foo<>::let_me_alone() até o tempo de instanciação.

Mas se não usarmos algo que depende da instanciação específica, o código deve ser bom. Portanto, o seguinte não é bem formado:

 $ cat non-dependent.cc template  struct Foo { void I_wont_compile() { Mine->is->valid->code. get->off->my->lawn; } }; int main () {} // note: no single instantiation 

Mine é um símbolo completamente desconhecido para o compilador, diferente this , para o qual o compilador poderia determinar sua dependência de instância.

O ponto chave aqui é que o C ++ usa um modelo de pesquisa de duas fases , onde faz a verificação de código não dependente na primeira fase, e a verificação semântica de código dependente é feita na fase dois (e tempo de instanciação). é também um conceito muitas vezes incompreendido ou desconhecido, muitos programadores de C ++ assumem que os modelos não são analisados ​​até a instanciação, mas isso é apenas mito vindo de, …, Microsoft C ++).

Instanciação completa de modelos de class

A definição de Foo<>::let_me_stay() funcionou porque a verificação de erros foi adiada para mais tarde, como para o ponteiro this , que é dependente. Exceto quando você teria feito uso de

instanciações explícitas

 cat > foo.cc #include  template  struct Foo { void let_me_stay() { this->is->valid->code. get->off->my->lawn; } void fun() { std::cout << "fun()" << std::endl; } }; template struct Foo; int main () { Foo foo; foo.fun(); } g++ foo.cc error: error: 'struct Foo' has no member named 'is' 

Definições de modelo em diferentes unidades de tradução

Quando você explicitamente instanciar, você instancia explicitamente. E torne todos os símbolos visíveis para o vinculador, o que também significa que a definição do modelo pode residir em diferentes unidades de tradução:

 $ cat A.cc template  struct Foo { void fun(); // Note: no definition }; int main () { Foo().fun(); } $ cat B.cc #include  template  struct Foo { void fun(); }; template  void Foo::fun() { std::cout << "fun!" << std::endl; } // Note: definition with extern linkage template struct Foo; // explicit instantiation upon void $ g++ A.cc B.cc $ ./a.out fun! 

No entanto, você deve explicitamente instanciar todos os argumentos de modelo a serem usados, caso contrário,

 $ cat A.cc template  struct Foo { void fun(); // Note: no definition }; int main () { Foo().fun(); } $ g++ A.cc B.cc undefined reference to `Foo::fun()' 

Pequena nota sobre a pesquisa em duas fases: se um compilador realmente implementa a pesquisa em duas fases, não é determinado pelo padrão. Para estar em conformidade, no entanto, deve funcionar como se assim fosse (assim como a adição ou multiplicação não precisa necessariamente ser executada usando instruções de CPU de adição ou multiplicação.

Edit : Acontece que o que eu escrevi abaixo é contrário ao padrão C ++. É verdadeiro para o Visual C ++, mas falso para compiladores que usam “pesquisa de nome de duas fases”.

Tanto quanto sei, o que você diz está correto. Modelos serão instanciados quando realmente utilizados (incluindo quando declarados como um membro de outro tipo, mas não quando mencionados em uma declaração de function (que não tem um corpo)) ou como resultado de instanciações explícitas.

Um problema com modelos é que se você usar o mesmo modelo (por exemplo, vetor) em várias unidades de compilation diferentes (arquivos .cpp), o compilador repete o trabalho de instanciar o modelo em cada arquivo .cpp, diminuindo a compilation. IIRC, o GCC tem algum mecanismo (não padrão?) Que pode ser usado para evitar isso (mas eu não uso o GCC). Mas o Visual C ++ sempre repete esse trabalho, a menos que você use instanciação de modelo explícita em um header pré-compilado (mas mesmo isso diminuirá a compilation, pois um arquivo PCH maior demora mais para carregar). Depois, o vinculador elimina as duplicatas. Nota : um comentário abaixo ligado a uma página que nos diz que nem todos os compiladores operam dessa maneira. Alguns compiladores adiam a instanciação de funções até o momento do link, que deve ser mais eficiente.

Um modelo não é totalmente instanciado quando é usado pela primeira vez. Em particular, as funções no modelo não são instanciadas até que sejam realmente chamadas. Você pode facilmente verificar isso adicionando uma function sem sentido a um modelo que você está usando ativamente:

 void Test() { fdsh "sw" = 6; wtf? } 

Você não receberá um erro a menos que instancie o modelo explicitamente ou tente chamar a function.

Espero que bibliotecas estáticas (e arquivos de object) armazenem o código de object de todos os modelos que foram instanciados. Mas se o seu programa tem uma certa biblioteca estática como uma dependência, você não pode realmente chamar as funções de modelo que já foram instanciadas nele, pelo menos não no VC ++, que sempre requer o código fonte (com corpos de function) de uma class de modelo. para chamar funções nele.

Eu não acho que é possível chamar uma function de modelo em uma biblioteca compartilhada (quando você não tem o código-fonte da function de modelo que você deseja chamar).