Templates y generic programming
Los templates no son macros ni runtime polymorphism. Son familias parametrizadas de funciones, clases, aliases, variables y concepts que el compilador convierte en specializations concretas cuando se usan. Generic programming en C++ significa escribir código contra un set de operaciones y dejar que el type system chequee si cada tipo concreto satisface ese contrato. Los concepts de C++20 hacen que ese contrato tenga nombre en vez de quedar escondido dentro del fallout de errores de template.
El reset: un template es source para generar código concreto chequeado. La abstracción existe en compile time; la specialization instanciada es lo que ve el linker.
Cómo funciona de verdad
Un function template describe una familia de funciones:
template <typename T>
T max_value(T left, T right);
Cuando el compilador ve max_value(1, 2), puede deducir T = int e instanciar una
specialization int. Cuando ve max_value(1.0, 2.0), puede instanciar una specialization
double. Un class template funciona igual para tipos como std::vector<int> o
Ring<std::string, 3>. Non-type template parameters como 3 son valores de compile time
que pueden moldear layout de storage y código generado.
| Pieza de template | Qué significa |
|---|---|
| Type parameter | un tipo elegido por el caller o por deduction |
| Non-type parameter | un valor de compile time como tamaño, puntero, enum o valor estructural |
| Function template | una familia de funciones seleccionadas por deduction y overload resolution |
| Class template | una familia de tipos, uno por lista de argumentos |
| Specialization | implementación custom para un patrón particular |
| Instantiation | el compilador crea la declaración/definición concreta que se necesita |
| Concept | predicado nombrado de compile time sobre template arguments |
Los templates se chequean en dos fases. El código no dependiente puede chequearse cuando se define el template. El código dependiente debe esperar hasta que se conozcan los argumentos. Por eso los errores pueden apuntar dentro de definiciones de templates aunque la llamada mala esté en otro lado. Los concepts mejoran esto chequeando los requirements intencionados antes de que el compilador atraviese cada expresión del cuerpo del template.
Las definiciones suelen vivir en headers porque la instanciación implícita necesita la
definición completa en el use site. Eso es distinto de funciones C/C++ no-template
ordinarias, donde una declaración puede vivir en un header y un .c o .cpp owns la
definición. Explicit instantiation puede mover parte del código de template de vuelta a un
.cpp, pero el modelo mental por defecto es implementación visible en headers y una
specialization generada por cada set de argumentos usado, con copias duplicadas fusionadas
por la toolchain en link time.
Artefacto ejecutable: un algoritmo, instanciaciones concretas
El demo ejecutable vive en
examples/modern-cpp/templates-and-generic-programming/. Usa un concept C++20, un function
template sobre std::span, un class template con un parámetro de tamaño non-type e
if constexpr para branching de compile time.
cd examples/modern-cpp/templates-and-generic-programming
./run.sh
El concept nombra la operación que necesita el algoritmo:
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
template <Addable T>
T total(std::span<const T> values) {
T result{};
for (const T &value : values) {
result = result + value;
}
return result;
}
El class template mete la capacidad dentro del tipo:
template <typename T, std::size_t N>
class Ring {
public:
void push(T value);
std::size_t size() const;
const T &back() const;
private:
std::array<T, N> items_{};
};
Ring<std::string, 3> y Ring<int, 3> serían tipos distintos. Ningún field de runtime
tiene que guardar N; es parte del tipo y puede moldear el layout. El script también emite
demo.O2.s, útil en Compiler Explorer o un editor local: buscá el código concreto generado
para total<int> y total<double>.
Fallas típicas y trade-offs
- Las definiciones de templates afectan el build time. Implementación visible en headers significa que muchas translation units pueden parsear el mismo código genérico.
- Puede haber code bloat. Un cuerpo de template instanciado para muchos tipos grandes o muchos valores non-type puede aumentar el tamaño del binario.
- Los diagnósticos todavía pueden ser profundos. Concepts mejora la puerta de entrada, pero código dependiente, overload sets y templates anidados pueden seguir produciendo errores largos.
- El contrato es estructural. Un tipo satisface un template soportando las operaciones requeridas, no declarando explícitamente "implements interface" salvo que un concept lo diga.
- La specialization puede fracturar comportamiento. Una specialization ingeniosa puede violar las assumptions que los callers aprendieron del primary template.
- El código genérico puede esconder operaciones caras.
T result = result + valuees barato paraint, quizá caro para una matriz grande y quizá incorrecto para un tipo con overloads sorprendentes.
En la práctica
- Empezá con funciones ordinarias. Usá un template cuando el mismo algoritmo realmente aplica a múltiples tipos y el código generado importa.
- Nombrá requirements con concepts. Un buen concept convierte "explosión de templates" en un contrato legible de compile time.
- Preferí requirements de tipos valor. El código genérico es más fácil de razonar cuando los tipos son regulares: constructibles, movibles, comparables y poco sorprendentes.
- Mantené chicos los templates en bordes. Poné APIs no-template estables alrededor de internals genéricos pesados cuando importan compile times y ABI.
- Inspeccioná instanciaciones cuando importa performance. Los templates pueden inlinear hermoso o multiplicar tamaño de código. Chequeá assembly generado, símbolos y tamaño del binario.
Conecta con: Abstracciones de costo cero (y dónde leakéan) · La STL: containers, iterators, algorithms · Referencias, const y overloading · Símbolos: definición, referencia y resolución · Object files y qué hay adentro · El preprocessor: macros, includes y conditional compilation
Fuentes
- cppreference - Templates - template entities, parámetros, instantiation, specialization y notas de visibilidad en headers. https://en.cppreference.com/w/cpp/language/templates
- cppreference - Function templates - deduction, interacción con overloads, template arguments explícitos y reglas de specialization. https://en.cppreference.com/w/cpp/language/function_template
- cppreference - Constraints and concepts - constraints C++20, requires-expressions y selección de overloads basada en concepts. https://en.cppreference.com/w/cpp/language/constraints
- C++ working draft -
[temp]- wording estándar para templates, instantiation, specialization y deduction. https://eel.is/c++draft/temp - C++ working draft -
[temp.constr]- wording estándar para constraints y concepts. https://eel.is/c++draft/temp.constr - C++ Core Guidelines - guía de templates alrededor de concepts, interfaces genéricas y mantener comprensible el código template. https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
- Compiler Explorer - compará instanciaciones de templates e inlining bajo compiladores reales. https://godbolt.org/