La covarianza y contravarianza en C++ con templates es un poco más manual que en otros lenguajes como Java o C#, porque C++ no tiene soporte directo para la varianza en plantillas. Pero hay formas de simularla o controlarla explícitamente usando herencia, punteros, referencias, std::variant, std::any, SFINAE y concepts.
Primero: ¿Qué es covarianza y contravarianza?
- Covarianza: cuando un tipo genérico Container<Derived> puede ser tratado como Container<Base>.
- Contravarianza: lo contrario Container<Base> puede ser usado donde se espera Container<Derived>.
- Invarianza: no se permite ninguna conversión.
Las Plantillas en C++ son invariantes, esto no es válido:
template <typename T>
class Caja<T> {};
class Animal {};
class Perro : public Animal {};
Caja<Perro>* cp = new Caja<Perro>();
Caja<Animal>* ca = cp; // ERROR: no se permite, son tipos distintos
Y listo, fin del post ... Pero pero sí hay covarianza con punteros y referencias a clases (no templates)
Esto funciona:
class Animal {
public:
virtual void hablar() {}
};
class Perro : public Animal {
public:
override void hablar() {}
};
Animal* a = new Perro(); // Anda por Covarianza de punteros
¿Cómo resolver o simular varianza con templates?
Usar herencia con punteros o referencias
template <typename T>
class Caja {
public:
T* valor;
};
Perro* p = new Perro();
Caja<Perro> c1;
c1.valor = p;
Caja<Animal> c2 = c1; // No compila directamente
Podemos hacer funciones que trabajen con T* o T& para aprovechar la covarianza de punteros.
Hacer una jerarquía polimórfica manual
class ICajaBase {
public:
virtual ~ICajaBase() = default;
};
template <typename T>
class Caja : public ICajaBase {
public:
T valor;
};
Luego podemos trabajar con ICajaBase* y aplicar RTTI o dynamic_cast.
Usar std::variant o std::any para manejar múltiples tipos
#include <variant>
std::variant<Perro, Gato> animal;
Esto evita plantillas covariantes, pero permite flexibilidad en tiempo de ejecución.
Usar std::enable_if, SFINAE o concepts para limitar compatibilidades
template <typename T>
class Caja {
public:
static_assert(std::is_base_of<Animal, T>::value, "T debe ser un Animal");
};
En C++, las plantillas son invariantes. pero podemos usar covarianza con punteros y referencias a clases base. Si queremos modelar jerarquías con templates, tenemos que hacerlo manualmente usando herencia, punteros, std::variant o SFINAE.