Cuando trabajas con plantillas de clase (templates), la sintaxis puede volverse verbosa y redundante. La Deducción de argumentos de clase (CTAD) [C++17] es el mecanismo que permite al compilador inferir los argumentos de una plantilla directamente desde los argumentos pasados al constructor, eliminando la necesidad de escribirlos explícitamente.
Normalmente, si tienes un std::pair<int, double>, estás obligado a decirle al compilador exactamente qué tipos vas a usar. Con CTAD, el compilador analiza los tipos de los valores que le entregas al constructor y deduce los parámetros de la plantilla por ti. Esto ocurre gracias a que el compilador asocia los tipos de los argumentos del constructor con los parámetros de la plantilla mediante un proceso de resolución de tipos. Es fundamental para escribir código más limpio (siguiendo el principio DRY: Don’t Repeat Yourself) y para facilitar el uso de contenedores o estructuras complejas.
Sin embargo, CTAD no es magia: solo funciona si el tipo que quieres deducir está presente en la firma de los argumentos del constructor. Si intentas instanciar una clase cuyo parámetro de plantilla no aparece en los argumentos del constructor, el compilador se rendirá. Además, cuando la deducción estándar no es suficiente para lo que necesitas (por ejemplo, cuando quieres que un std::vector<T> resulte en un Wrapper<T> en lugar de un Wrapper<std::vector<T>>), debemos recurrir a las Guías de deducción (deduction guides).
#include <iostream>
#include <vector>
#include <string>
#include <utility>
// 1. Clase con CTAD estándar
// El compilador deducirá T basándose en el constructor.
template <typename T>
struct Box {
T value;
explicit Box(T v) : value(std::move(v)) {}
};
// 2. Uso de Guías de Deducción (Deduction Guides)
// Queremos que si pasamos un std::vector<T>, el Wrapper sea Wrapper<T>
// y no Wrapper<std::vector<T>>.
template <typename T>
struct Wrapper {
T data;
explicit Wrapper(T d) : data(std::move(d)) {}
};
// Guía de deducción personalizada:
// "Si el constructor recibe un std::vector<T>, deduce Wrapper<T>"
template <typename T>
Wrapper(std::vector<T>) -> Wrapper<T>;
// 3. CTAD para Agregados (C++20)
// En C++20, las estructuras agregadas que son plantillas también permiten CTAD.
template <typename T>
struct Point {
T x, y;
};
int main() {
// --- CTAD Estándar ---
// Deduce automáticamente std::pair<int, double>
std::pair p(10, 3.14);
std::cout << "Pair: " << p.first << ", " << p.second << "\n";
// Deduce Box<int>
Box b(100);
std::cout << "Box value: " << b.value << "\n";
// --- CTAD con Guía de Deducción ---
std::vector<float> vec = {1.1f, 2.2f, 3.3f};
// Sin la guía, Wrapper sería Wrapper<std::vector<float>>.
// Con la guía, Wrapper es Wrapper<float> (tomando el tipo del elemento).
Wrapper w(vec);
std::cout << "Wrapper data (size): " << w.data.size() << " (type inferred via guide)\n";
// --- Agregados (C++20) ---
// Deduce Point<double>
Point pt{10.5, 20.0};
std::cout << "Point: (" << pt.x << ", " << pt.y << ")\n";
return 0;
}
Para compilar este ejemplo:
g++ -std=c++20 -Wall -Wextra -Wpedantic -o example example.cpp
Desglose del código
Fíjate en cómo opera la inteligencia del compilador en cada caso:
-
std::pair p(10, 3.14);: Aquí ocurre la deducción estándar. El compilador mira el constructor destd::pairy ve que recibe uninty undouble. Por lo tanto, instanciastd::pair<int, double>. Es el caso de uso más común y limpio. -
La Guía de Deducción (
Wrapper(std::vector<T>) -> Wrapper<T>): Este es el punto crítico. Por defecto, si intentas usar CTAD conWrapper w(vec);dondeveces unstd::vector<float>, el compilador deduciría queTesstd::vector<float>, resultando en unWrapper<std::vector<float>>. Al añadir la guía de deducción, le estamos dando una instrucción explícita al compilador: “Si el argumento que recibes es unstd::vector<T>, entonces el tipo de la clase debe serWrapper<T>“. Esto permite extraer el tipo del contenido del contenedor durante la deducción. -
Point pt{10.5, 20.0};: Esto es una característica de C++20. Antes de C++20, las estructuras agregadas (clases sin constructores declarados, con miembros públicos y sin herencia) no podían beneficiarse de CTAD. Ahora, el compilador analiza la lista de inicialización{...}y los tipos de los miembros para deducirT.
El error frecuente
Un error muy común ocurre cuando intentas deducir un parámetro de la plantilla que no está presente en los argumentos del constructor. El compilador no puede “adivinar” tipos que no se mencionan en la llamada.
template <typename T>
struct Problema {
T valor;
// El constructor recibe un int, pero no hay forma de deducir T
// a menos que el usuario la especifique explícitamente.
explicit Problema(int n) : valor(static_cast<T>(n)) {}
};
// Error de compilación: no se puede deducir el argumento de plantilla para 'Problema'
// Problema p(10);
// Correcto:
Problema<int> p(10);
Este error suele pasar desapercibido hasta que intentas simplificar código de plantillas. Si la lógica de tu clase depende de un tipo T que se usa internamente para un miembro o un return type, pero no aparece en el constructor, estás obligado a usar la sintaxis clásica Clase<T> nombre(args);.
Si intentas usar CTAD con constructores por defecto (sin argumentos), el compilador fallará porque no hay “fuente de verdad” para la deducción.
N° 139