template c++ templates constructor traits software-design

c++ - template - Constructores, plantillas y parámetros no tipográficos.



template add c++ (3)

Tengo una clase que debe depender, por alguna razón, de un parámetro de plantilla int .
Por las mismas razones, ese parámetro no puede ser parte de la lista de parámetros para la clase, en cambio, es parte de la lista de parámetros de su constructor (es decir, por supuesto, con plantilla).

Aquí surgieron los problemas.
Tal vez me esté faltando algo, pero no puedo ver una manera fácil de proporcionar dicho parámetro al constructor, porque no se puede deducir ni especificar explícitamente.

Hasta ahora, he encontrado las siguientes alternativas:

  • poner el parámetro mencionado anteriormente en la lista de parámetros de la clase

  • cree un método de fábrica o una función de fábrica que pueda invocarse como ejemplo como factory<42>(params)

  • Proporcionar una estructura de rasgos al constructor.

Intenté crear un ejemplo de trabajo (no tan) mínimo para la última solución mencionada, también para explicar mejor el problema.
La clase en el ejemplo no es una clase de plantilla por sí misma, ya que el punto clave es el constructor, de todos modos la clase real es una clase de plantilla.

#include<iostream> #include<array> template<int N> struct traits { static constexpr int size = N; }; class C final { struct B { virtual ~B() = default; virtual void foo() = 0; }; template<int N> struct D: public B{ void foo() { using namespace std; cout << N << endl; } std::array<int, N> arr; }; public: template<typename T> explicit C(T) { b = new D<T::size>{}; } ~C() { delete b; } void foo() { b->foo(); } private: B *b; }; int main() { C c{traits<3>{}}; c.foo(); }

Para ser honesto, ninguna de las soluciones mencionadas anteriormente encaja bien:

  • mover el parámetro a la lista de parámetros de la clase rompe completamente su diseño y no es una solución viable

  • Un método de fábrica es algo que me gustaría evitar, pero podría resolver el problema.

  • La estructura de rasgos parece ser la mejor solución hasta ahora, pero de alguna manera no estoy completamente satisfecho

La pregunta es bastante fácil: ¿hay algo que me perdí, tal vez una solución más fácil y elegante, un detalle del lenguaje que olvidé por completo, o son los tres enfoques mencionados anteriormente los que debo elegir?
Cualquier sugerencia sería apreciada.


Creo que la solución con "rasgos" es la mejor para la mayoría de los casos.

Solo para hacer un poco más de "desorden" en este tema, proporcionaré dos alternativas más. Tal vez en algunos casos muy específicos, pueden ser de alguna manera mejores.

  1. Variable global de plantilla: puede nombrarla solución prototipo :

La clase C solo se diferencia en su constructor de su código original:

class C final { // All B and D defined as in OP code public: // Here the change - c-tor just accepts D<int> template <int size> explicit C(D<size>* b) : b(b) {} // all the rest as in OP code };

El prototipo - plantilla de variable global:

template <int N> C c{new C::D<N>()}; // this variable should be rather const - but foo() is not const // and copy semantic is not implemented...

Y uso:

int main() { // you did not implement copy semantic properly - so just reference taken C& c = ::c<3>; c.foo(); }

  1. Solución con clase base - y clase derivada según int

Esta solución, aunque parece bastante prometedora, la evitaría personalmente, que solo complica el diseño, y aquí también existe alguna posibilidad de corte de objetos.

class CBase { // all here as in OP code for C class public: // only difference is this constructor: template<int size> explicit CBase(D<size>* b) : b(b) {} };

Entonces - la clase final:

template <int N> class C final : private CBase { public: C() : CBase(new CBase::D<N>()) {} using CBase::foo; };

El uso:

int main() { C<3> c; c.foo(); }

P: Uno puede preguntar de qué manera la solución con la clase Base es mejor que simplemente agregar int como otro parámetro.
R: según la clase de implementación base, no es necesario que tenga muchas "copias" del mismo código; se evita la infiltración del código de la plantilla ...


Tienes que pasar algo que se puede deducir. Lo más sencillo de usar es simplemente un contenedor vacío para int: std::integral_constant . Ya que solo quieres int s, creo, podemos crear un alias y solo aceptar ese tipo específico:

template <int N> using int_ = std::integral_constant<int, N>;

Donde tu constructor de C simplemente acepta que:

template <int N> explicit C(int_<N> ) { b = new D<N>{}; } C c{int_<3>{}};

Incluso podría hacer todo lo posible y crear un literal definido por el usuario para esto (a la Boost.Hana) para que pueda escribir:

auto c = 3_c; // does the above

También, considera simplemente enviar el rasgo a D La metaprogramación funciona mejor si todo es un tipo. Es decir, todavía acepta el mismo int_ en C :

template <class T> explicit C(T ) { b = new D<T>{}; }

Donde ahora D espera algo que tiene un ::value :

template <class T> struct D: public B{ static constexpr int N = T::value; void foo() { using namespace std; cout << N << endl; } std::array<int, N> arr; };

Es lo mismo en cualquier caso desde la perspectiva del usuario de C , pero vale la pena pensarlo.


Usar plantilla de especialización y herencia:

#include <iostream> using namespace std; template <int num> struct A { A() { cout << "generic" << endl; } }; template <> struct A<1> { A() { cout << "implementation of 1" << endl; } }; template <int num> struct B : public A<num> { B() : A<num>() {} }; int main(int argc, char *argv[]) { B<1> b; B<3> b1; B<4> b2; }

Edición: o puedes hacerlo aún más fácil:

template <int num> struct A { A(); }; template <int num> A<num>::A() { cout << "general " << num << endl; } template <> A<1>::A() { cout << "specialization for 1" << endl; }