Class Templates y CTAD
Construye estructuras de datos genericas con class templates. Comprende las definiciones de miembros, los argumentos por defecto, y como el CTAD de C++17 te permite omitir los argumentos de template.
Class Templates
Un class template define una familia de clases parametrizadas por uno o mas tipos (o valores non-type). La biblioteca estandar esta construida sobre class templates: std::vector, std::map, std::optional, std::shared_ptr. Cuando escribes std::vector, el compilador instancia toda la clase con T = int, generando un tipo concreto.
Los class templates siguen las mismas reglas que los function templates: deben definirse en headers, y cada conjunto unico de argumentos de template produce un tipo distinto. Stack y Stack son tipos completamente no relacionados — no comparten relacion de herencia.
Un Stack Generico
Aqui hay un stack generico simple. Observa como las funciones miembro definidas fuera del cuerpo de la clase requieren repetir la lista de parametros de template.
#include <vector>
#include <stdexcept>
#include <iostream>
template <typename T>
class Stack {
private:
std::vector<T> elements_;
public:
void push(const T& value);
void pop();
const T& top() const;
bool empty() const { return elements_.empty(); }
std::size_t size() const { return elements_.size(); }
};
// Member definitions outside the class require the template header
template <typename T>
void Stack<T>::push(const T& value) {
elements_.push_back(value);
}
template <typename T>
void Stack<T>::pop() {
if (elements_.empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
elements_.pop_back();
}
template <typename T>
const T& Stack<T>::top() const {
if (elements_.empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elements_.back();
}
int main() {
Stack<int> intStack;
intStack.push(42);
intStack.push(17);
std::cout << intStack.top() << '\n'; // 17
intStack.pop();
std::cout << intStack.top() << '\n'; // 42
}Multiples Parametros y Valores por Defecto
Los class templates pueden tener multiples parametros de template y argumentos de template por defecto, al igual que los parametros de funcion pueden tener valores por defecto. Los argumentos por defecto se resuelven de izquierda a derecha.
#include <vector>
#include <deque>
#include <iostream>
// Container defaults to std::deque<T> — matches std::stack's design
template <typename T, typename Container = std::deque<T>>
class Stack {
private:
Container elements_;
public:
void push(const T& value) { elements_.push_back(value); }
void pop() { elements_.pop_back(); }
const T& top() const { return elements_.back(); }
bool empty() const { return elements_.empty(); }
};
int main() {
Stack<int> s1; // uses deque<int>
Stack<int, std::vector<int>> s2; // uses vector<int>
s1.push(10);
s2.push(20);
std::cout << s1.top() << '\n'; // 10
std::cout << s2.top() << '\n'; // 20
}Class Template Argument Deduction (CTAD)
Antes de C++17, siempre tenias que especificar los argumentos de template al construir instancias de class templates: std::pair. CTAD (Class Template Argument Deduction), introducido en C++17, permite al compilador deducir los argumentos de template de la clase a partir de los argumentos del constructor, de la misma manera que la deduccion de argumentos de function templates.
Con CTAD: std::pair p(1, 3.14); — el compilador deduce std::pair. Esto funciona para std::vector, std::tuple, std::optional, std::array (C++20), y tus propios class templates.
CTAD usa guias de deduccion — ya sean implicitas (generadas a partir de constructores) o explicitas (definidas por el usuario). El compilador prueba cada guia y elige la mejor coincidencia.
CTAD y Guias de Deduccion
Las guias de deduccion definidas por el usuario le indican al compilador como mapear los argumentos del constructor a los argumentos de template. Son especialmente utiles cuando el tipo del argumento del constructor no corresponde directamente al parametro de template.
#include <iostream>
#include <cstddef>
template <typename T, std::size_t N>
struct Array {
T data[N];
};
// Without a deduction guide, CTAD cannot deduce T and N.
// This explicit deduction guide maps brace-init with N elements
// of the same type T to Array<T, N>.
template <typename First, typename... Rest>
Array(First, Rest...) -> Array<First, 1 + sizeof...(Rest)>;
// Standard library example: std::vector CTAD from iterators
#include <vector>
#include <list>
int main() {
// CTAD with our custom deduction guide
Array arr{1, 2, 3, 4, 5}; // deduces Array<int, 5>
std::cout << arr.data[2] << '\n'; // 3
// Standard library CTAD examples (C++17)
std::pair p(42, 3.14); // pair<int, double>
std::vector v{1, 2, 3}; // vector<int>
std::tuple t(1, 2.0, 'a'); // tuple<int, double, char>
// CTAD from iterator range
std::list<int> lst{10, 20, 30};
std::vector v2(lst.begin(), lst.end()); // vector<int>
}Member Templates
Las funciones miembro de un class template pueden a su vez ser templates, agregando parametros de template adicionales. Esto se llama member template. Un caso de uso comun es habilitar la asignacion o construccion desde una instanciacion relacionada pero diferente.
Por ejemplo, podrias querer asignar un Stack desde un Stack — pero como estos son tipos no relacionados, la asignacion por copia por defecto no funcionara. Un operador de asignacion member template resuelve esto:
template <typename T>
class Stack {
public:
template <typename U>
Stack& operator=(const Stack<U>& other);
};Dentro de este member template, U puede diferir de T. El cuerpo puede copiar elementos de other siempre que U sea convertible a T. Este patron se usa extensamente en la biblioteca estandar — std::shared_ptr se convierte a std::shared_ptr mediante un constructor member template.
Cuando especializas un class template, la especializacion es una clase completamente nueva — no hereda ningun miembro del template primario. Debes redefinir cada funcion miembro y cada dato miembro. Olvidar redefinir un miembro en la especializacion resulta en un error de compilacion cuando se usa ese miembro. Ademas, la especializacion parcial solo esta disponible para class templates, no para function templates. Cubrimos la especializacion en detalle en el proximo capitulo.
- Los class templates definen familias de clases — cada conjunto unico de argumentos produce un tipo distinto
- Las funciones miembro definidas fuera de la clase deben repetir el encabezado
templatey usarClassName:: - Los argumentos de template por defecto funcionan de izquierda a derecha, igual que los argumentos por defecto de funciones
- CTAD (C++17) permite al compilador deducir los argumentos de template de la clase a partir de los argumentos del constructor
- Las guias de deduccion explicitas mapean patrones de argumentos del constructor a argumentos de template
- Los member templates permiten operaciones entre diferentes instanciaciones del mismo class template
Quiz — Pon a prueba tus conocimientos
(15 XP)1. Dado `template <typename T, typename Container = std::deque<T>> class Stack`, cual es el tipo de `Stack<int>`?
2. Que es CTAD (C++17)?
3. Cuando especializas un class template, que sucede con los miembros del template primario?