c++ class templates specialization

Especialización en plantilla de clase c++, sin tener que reimplementar todo.



class templates (5)

Tengo una clase templada como esta:

template<typename T> class A { protected: std::vector<T> myVector; public: /* constructors + a bunch of member functions here */ }

Me gustaría agregar solo UNA función miembro que funcionaría solo para 1 tipo dado de T. ¿Es posible hacer eso sin tener que especializar la clase y reimplementar todos los otros métodos ya existentes?

Gracias


Como una solución alternativa, que también funciona en C ++ 03 (a diferencia de las soluciones static_assert o enable_if ), puede agregar un argumento de plantilla predeterminado adicional que le permitirá tener una versión de clase especializada y no especializada. Entonces puedes heredar tu versión especializada de la no especializada.

Aquí hay un fragmento de muestra:

#include <vector> template<typename T, bool unspecialized = false> class A { protected: std::vector<T> myVector; public: void setVec(const std::vector<T>& vec) { myVector = vec; } /* constructors + a bunch of member functions here */ }; template<> class A<int, false> : public A<int, true> { public: int onlyForInt() { return 25; } }; int main() { // your code goes here std::vector<int> vec; A<int> a; a.setVec(vec); a.onlyForInt(); return 0; }

Los inconvenientes de esta solución es la necesidad de agregar promotores de constructores, si la clase tiene constructores no triviales.


La solución más simple y limpia es usar static_assert() en el cuerpo de un método, rechazando otros tipos que el seleccionado (en el ejemplo de abajo solo se aceptan enteros):

#include <type_traits> #include <vector> template <typename T> class A { public: void onlyForInts(T t) { static_assert(std::is_same<T, int>::value, "Works only with ints!"); } protected: std::vector<T> myVector; }; int main() { A<int> i; i.onlyForInts(1); // works ! A<float> f; //f.onlyForInts(3.14f); // does not compile ! }

OK CASE DEMO NOK CASE DEMO

Esto utiliza el hecho de que un compilador crea una instancia de una función miembro de una plantilla de clase solo cuando una se usa realmente (no cuando la plantilla de clase se crea una instancia de sí misma) . Y con la solución anterior, cuando un compilador intenta hacerlo, falla debido a la ejecución de un static_assert .

Referencia estándar de C ++:

§ 14.7.1 Creación de instancias implícita [temp.inst]

  1. A menos que una especialización de plantilla de función haya sido explícitamente instanciada o explícitamente especializada, la especialización de plantilla de función se crea implícitamente cuando se hace referencia a la especialización en un contexto que requiere que exista una definición de función . A menos que una llamada sea a una especialidad explícita de la plantilla de función o a una función miembro de una plantilla de clase explícitamente especializada, un argumento predeterminado para una plantilla de función o una función miembro de una plantilla de clase se crea una instancia implícita cuando se llama a la función en un contexto que requiere El valor del argumento por defecto.

  2. [ Ejemplo:

    template<class T> struct Z { void f(); void g(); }; void h() { Z<int> a; // instantiation of class Z<int> required Z<char>* p; // instantiation of class Z<char> not required Z<double>* q; // instantiation of class Z<double> not required a.f(); // instantiation of Z<int>::f() required p->g(); // instantiation of class Z<char> required, and // instantiation of Z<char>::g() required }

    Nada en este ejemplo requiere que la class Z<double> , Z<int>::g() o Z<char>::f() estén implícitamente instanciadas. - ejemplo final ]


La técnica static_assert de @PiotrS. funciona muy bien Pero también es bueno saber que puede especializar una función de miembro único sin duplicación de código. Solo dé a los onlyForInts() genéricos una implementación no onlyForInts() vacía, y especialícela fuera de clase para int

#include <vector> template <typename T> class A { public: void onlyForInts(T t) { // no-op } protected: std::vector<T> myVector; }; template<> void A<int>::onlyForInts(int t) { // works } int main() { A<int> i; i.onlyForInts(1); // works ! A<float> f; f.onlyForInts(3.14f); // compiles, but does nothing ! }

coliru.stacked-crooked.com/a/2f790496fb1b3cf3 .

Esta técnica es útil si desea tener un comportamiento específico int sin deshabilitar completamente el comportamiento genérico.


Sí, es posible en C ++ 03 con CRTP ( patrón de plantilla curiosamente recurrente ):

#include <numeric> #include <vector> template<typename Derived, typename T> struct Base { }; template<typename Derived> struct Base<Derived, int> { int Sum() const { return std::accumulate(static_cast<Derived const*>(this)->myVector.begin(), static_cast<Derived const*>(this)->myVector.end(), int()); } }; template<typename T> class A : public Base<A<T>, T> { friend class Base<A<T>, T>; protected: std::vector<T> myVector; public: /* constructors + a bunch of member functions here */ }; int main() { A<int> Foo; Foo.Sum(); }


Un enfoque aún no dado en las respuestas es usar la biblioteca estándar std::enable_if para realizar SFINAE en una clase base que herede a la clase principal que define las funciones miembro apropiadas.

Código de ejemplo:

template<typename T, class Enable = void> class A_base; template<typename T> class A_base<T, typename std::enable_if<std::is_integral<T>::value>::type>{ public: void only_for_ints(){/* integer-based function */} }; template<typename T> class A_base<T, typename std::enable_if<!std::is_integral<T>::value>::type>{ public: // maybe specialize for non-int }; template<typename T> class A: public A_base<T>{ protected: std::vector<T> my_vector; };

Este enfoque sería mejor que una función vacía porque está siendo más estricto con su API y mejor que con static_cast porque simplemente no llegará al interior de la función (no existirá) y le dará una buena idea. mensaje de error en tiempo de compilación (GCC muestra "no tiene ningún miembro llamado ''only_for_ints''" en mi máquina).

La desventaja de este método sería el tiempo de compilación y el código inflado, pero no creo que sea demasiado fuerte.

(¡No te atrevas a decir que el requisito de C ++ 11 es un inconveniente, estamos en 2014, maldita sea, y el próximo estándar ya se ha finalizado!)

También, noté, probablemente tendrá que definir my_vector en la clase base en lugar de la final porque probablemente quiera manejar esos datos dentro de la función miembro.

Una buena forma de hacerlo sin duplicar un montón de código es crear una clase base (buen dios) y heredar esa clase en la clase base.

Ejemplo:

template<typename T> class base_data{ protected: std::vector<T> my_vector; }; template<typename T> class A_base<T, typename std::enable_if<std::is_integral<T>::value>::type>: public base_bata<T>{ public: void only_for_ints(){/* phew, finally. fiddle around with my_vector! */} }; // non-integer A-base template<typename T> class A: public A_base<T>{ protected: // helper functions not available in base };

Eso deja un horrible esquema de herencia múltiple, pero es muy funcional y facilita la definición de los miembros en función de los parámetros de la plantilla (para futuras pruebas).

A la gente a menudo no le gusta la herencia múltiple o lo complicado que se ve el SFINAE, pero no podría vivir sin él ahora que lo sé: ¡la velocidad del código estático con el polimorfismo del código dinámico!