Eu estava lendo sobre funções de modelo e fiquei confuso com este problema:
#include <iostream>
void f(int) {
std::cout << "f(int)\n";
}
template<typename T>
void g(T val) {
std::cout << typeid(val).name() << " ";
f(val);
}
void f(double) {
std::cout << "f(double)\n";
}
template void g<double>(double);
int main() {
f(1.0); // f(double)
f(1); // f(int)
g(1.0); // d f(int), this is surprising
g(1); // i f(int)
}
Os resultados são os mesmos se eu não escrever template void g<double>(double);
.
Eu acho que g<double>
deveria ser instanciado depois f(double)
e, portanto, a chamada para f
in g
deveria chamar f(double)
. Surpreendentemente, ele ainda chama f(int)
no g<double>
. Alguém pode me ajudar a entender isso?
Depois de ler as respostas, descobri qual é realmente a minha confusão.
Aqui está um exemplo atualizado. É praticamente inalterado, exceto que eu adicionei uma especialização para g<double>
:
#include <iostream>
void f(int){cout << "f(int)" << endl;}
template<typename T>
void g(T val)
{
cout << typeid(val).name() << " ";
f(val);
}
void f(double){cout << "f(double)" << endl;}
//Now use user specialization to replace
//template void g<double>(double);
template<>
void g<double>(double val)
{
cout << typeid(val).name() << " ";
f(val);
}
int main() {
f(1.0); // f(double)
f(1); // f(int)
g(1.0); // now d f(double)
g(1); // i f(int)
}
Com a especialização do usuário, g(1.0)
comporta-se como eu esperava.
O compilador não deve fazer automaticamente essa mesma instanciação g<double>
no mesmo local (ou mesmo depois main()
, conforme descrito na seção 26.3.3 da Linguagem de programação C ++ , 4ª edição)?
fonte
g(1)
, dái f(int)
para mim. Você escreveud f(double)
. Isso foi um erro de digitação?Respostas:
O nome
f
é um nome dependente (dependeT
do argumentoval
) e será resolvido em duas etapas :void f(double)
não é visível no contexto de definição do modelo e a ADL também não o encontrará, porquePodemos modificar um pouco o seu exemplo:
Agora, o ADL encontrará
void f(Double)
na segunda etapa e a saída será6Double f(Double)
. Podemos desativar a ADL escrevendo(f)(val)
(ou::f(val)
) em vez def(val)
. Então a saída será6Double f(Int)
, de acordo com o seu exemplo.fonte
void f(double)
não é visível - esse contexto termina antes de sua declaração. Na etapa 2, o ADL não encontrará nada; portanto, o contexto de instanciação do modelo não desempenha nenhum papel aqui.void f(double)
, então essa função é visível a partir dela. Agoraf
não é um nome dependente. Se houver uma correspondência melhor paraf(val);
declarada após a definição deg<double>
, ela também não será encontrada. A única maneira de "olhar adiante" é o ADL (ou algum compilador antigo que não implementa a pesquisa em duas fases corretamente).main()
. Eles não verãof(double)
, porque quando a instanciação acontece, é tarde demais: a primeira fase da pesquisa já foi realizada e não foi encontradaf(double)
.O problema é
f(double)
que não foi declarado no ponto em que você o chama; se você mover sua declaração para frentetemplate g
, ela será chamada.Edit: Por que alguém usaria instanciação manual?
(Vou falar apenas sobre modelos de função, também há argumentos análogos para modelos de classe.) O uso principal é reduzir o tempo de compilação e / ou ocultar o código do modelo dos usuários.
O programa C ++ é incorporado em binários em 2 etapas: compilação e vinculação. Para a compilação de uma chamada de função ter sucesso, apenas o cabeçalho da função é necessário. Para que a vinculação seja bem-sucedida, é necessário um arquivo de objeto contendo o corpo compilado da função.
Agora, quando o compilador vê uma chamada de uma função de modelo , o que ele faz depende se conhece o corpo do modelo ou apenas o cabeçalho. Se ele vir apenas o cabeçalho, fará o mesmo que se a função não estivesse modelada: coloca informações sobre a chamada do vinculador para o arquivo de objeto. Mas se ele também vê o corpo do modelo, ele também faz outra coisa: instancia a instância apropriada do corpo, compila esse corpo e o coloca no arquivo de objeto também.
Se vários arquivos de origem chamarem a mesma instância da função de modelo, cada um de seus arquivos de objeto conterá uma versão compilada da instância da função. (O Linker sabe disso e resolve todas as chamadas para uma única função compilada, portanto, haverá apenas uma no binário final do programa / biblioteca.) No entanto, para compilar cada um dos arquivos de origem, a função teve que ser instanciada e compilado, o que levou tempo.
É suficiente para o vinculador fazer seu trabalho se o corpo da função estiver em um arquivo de objeto. Instanciar manualmente o modelo em um arquivo de origem é uma maneira de fazer o compilador colocar o corpo da função no arquivo de objeto do arquivo de origem em questão. (É como se a função fosse chamada, mas a instanciação é escrita em um local onde a chamada da função seria inválida.) Quando isso é feito, todos os arquivos que chamam a sua função podem ser compilados sabendo apenas o cabeçalho da função, portanto economizando tempo para instanciar e compilar o corpo da função com cada uma das chamadas.
A segunda razão (ocultação da implementação) pode fazer sentido agora. Se um autor da biblioteca deseja que os usuários de sua função de modelo possam usar a função, ela geralmente fornece o código do modelo, para que eles mesmos possam compilá-lo. Se ela quisesse manter em segredo o código fonte do modelo, ela poderia instanciar manualmente o modelo no código que ela usa para criar a biblioteca e fornecer aos usuários a versão do objeto assim obtida em vez da fonte.
Isto faz algum sentido?
fonte
template void g<double>(double);
chamada instanciação manual (observe a opçãotemplate
sem colchetes angulares, esse é um recurso distintivo da sintaxe); que diz ao compilador para criar uma instância do método Aqui, ele tem pouco efeito; se não estivesse lá, o compilador geraria a instância no local em que a instância é chamada. A instanciação manual raramente é usada, vou dizer por que você pode querer usá-la depois de confirmar que a coisa está mais clara agora :-) #