Templates C++: por que seu código não linka (e como corrigir isso)
\nVocê já escreveu uma função template elegante, colocou cuidadosamente a declaração no .h, a definição no .cpp, compilou o projeto... e recebeu o erro ameaçador do linker? undefined reference — um abraço de 1998. Familiar? Se sim, você está no lugar certo. Se não, ou você é um gênio, ou nunca tentou separar templates. Bem-vindo ao clube.
Neste artigo, vamos analisar a pergunta mais popular do Stack Overflow (sim, aquela com milhares de visualizações): «Storing C++ template function definitions in a .CPP file». Não vamos apenas copiar a resposta — vamos entender por que funciona dessa forma e aprender a aplicar na prática. Vamos lá!
\n\nO problema: templates não são funções comuns
\nImagine que você está escrevendo uma biblioteca em C++ (ou apenas um projeto educacional para PythonLib). Você tem uma função template max:
// max.h\ntemplate <typename T>\nT max(T a, T b);\n// max.cpp\ntemplate <typename T>\nT max(T a, T b) {\n return (a > b) ? a : b;\n}\n// main.cpp\n#include "max.h"\nint main() {\n int result = max(3, 7); // Erro do linker!\n return 0;\n}\nO compilador de main.cpp vê apenas a declaração no .h. Ele gera a chamada da função max<int>. Mas onde está o corpo? Em max.cpp — mas o compilador não sabe que int será necessário! Ele compila max.cpp separadamente e, não vendo nenhuma chamada a max<int>, simplesmente não cria essa especialização. O resultado é um erro na fase de linkagem.
Ideia chave: um template não é código, é um molde para código. Até que o compilador veja um tipo concreto, ele não gera código de máquina. Portanto, a separação clássica em .h e .cpp para templates não funciona de imediato.
Solução #1: Tudo no arquivo de cabeçalho (Header-Only)
\nA abordagem mais simples e frequentemente usada é colocar tanto a declaração quanto a definição no arquivo .h. Sim, isso aumenta o tempo de compilação (cada .cpp que inclui esse header irá instanciar o template novamente), mas não há problemas com linkagem.
// max.h (header-only)\n#ifndef MAX_H\n#define MAX_H\n\ntemplate <typename T>\nT max(T a, T b) {\n return (a > b) ? a : b;\n}\n\n#endif\nPrós: simplicidade, sem erros de linkagem, ideal para projetos pequenos ou bibliotecas onde templates são usados raramente.
\nContras: inchaço do código na compilação, aumento do tempo de build, impossibilidade de ocultar a implementação (se você distribuir a biblioteca como binário compilado).
\n\nSolução #2: Instanciação explícita (Explicit Instantiation)
\nE aqui está o herói do nosso artigo. Se você sabe de antemão com quais tipos o template será usado, pode pedir explicitamente ao compilador para criar essas especializações no arquivo .cpp. Esta é a resposta para a pergunta «Storing C++ template function definitions in a .CPP file».
// max.h\n#ifndef MAX_H\n#define MAX_H\n\ntemplate <typename T>\nT max(T a, T b);\n\n// Informamos ao linker que as especializações estarão em outro lugar\nextern template int max(int, int);\nextern template double max(double, double);\n\n#endif\n// max.cpp\n#include "max.h"\n\ntemplate <typename T>\nT max(T a, T b) {\n return (a > b) ? a : b;\n}\n\n// Instanciamos explicitamente os tipos necessários\ntemplate int max(int, int);\ntemplate double max(double, double);\n// main.cpp\n#include "max.h"\nint main() {\n int a = max(3, 7); // OK\n double b = max(3.14, 2.71); // OK\n // float c = max(1.0f, 2.0f); // Erro