c++ - sirven - programar con plantillas
¿Cómo paso los parámetros de la plantilla a un CRTP? (1)
En el siguiente código:
template <typename T>
class CRTP
{
public:
};
template <int I, typename T>
class CRTPInt
{
public:
};
template <template <typename> class T>
class Derived : public T<Derived<T>>
{
public:
};
void main()
{
Derived<CRTP> foo;
Derived<CRTPInt<2>> foo2;
}
¿Cómo escribo CRPTInt para poder pasar un parámetro de templado que luego se continuará en la definición derivada?
Gracias,
Jim
El patrón de CRTP se usa generalmente para permitir el polimorfismo estático y la capacidad de mezclar (parametrizar) el comportamiento. Para ilustrar dos alternativas, es conveniente definir primero una plantilla general
template
<
typename Derived
>
class enable_down_cast
{
private:
// typedefs
typedef enable_down_cast Base;
public:
Derived const* self() const
{
// casting "down" the inheritance hierarchy
return static_cast<Derived const*>(this);
}
// write the non-const version in terms of the const version
// Effective C++ 3rd ed., Item 3 (p. 24-25)
Derived* self()
{
return const_cast<Derived*>(static_cast<Base const*>(this)->self());
}
protected:
// disable deletion of Derived* through Base*
// enable deletion of Base* through Derived*
~enable_down_cast() = default; // C++11 only, use ~enable_down_cast() {} in C++98
};
A continuación, define una plantilla de clase de interfaz para el tipo de comportamiento que desea
template<typename FX>
class FooInterface
:
// enable static polymorphism
public enable_down_cast< FX >
{
private:
// dependent name now in scope
using enable_down_cast< FX >::self;
public:
// interface
void foo() { self()->do_foo(); }
protected:
// disable deletion of Derived* through Base*
// enable deletion of Base* through Derived*
~IFooInterface() = default; // C++11 only, use ~IFooInterface() {} in C++98/03
};
Para obtener diferentes implementaciones de esta interfaz, simplemente defina las diferentes clases que cada una deriva de FooInterface
consigo mismas como parámetros de plantilla curiosamente recurrentes :
class FooImpl
:
public FooInterface< FooImpl >
{
private:
// implementation
friend class FooInterface< FooImpl > ;
void do_foo() { std::cout << "Foo/n"; }
};
class AnotherFooImpl
:
public FooInterface< AnotherFooImpl >
{
private:
// implementation
friend class FooInterface< AnotherFooImpl >;
void do_foo() { std::cout << "AnotherFoo/n"; }
};
La alternativa es parametrizar las diferentes implementaciones de una interfaz. Esta vez, la plantilla de clase depende tanto de un parámetro plantilla-plantilla como de un parámetro no-tipo
template<template<int> class F, int X>
class BarInterface
:
public enable_down_cast< F<X> >
{
private:
// dependent name now in scope
using enable_down_cast< F<X> >::self;
public:
// interface
void bar() { self()->do_bar(); }
protected:
// disable deletion of Derived* through Base*
// enable deletion of Base* through Derived*
~BarInterface() = default; // C++11 only, use ~BarInterface() {} in C++98/03
};
La implementación es entonces otra plantilla de clase, que se deriva de la interfaz tanto con el parámetro no-type como con los argumentos
template< int X >
class BarImpl
:
public BarInterface< BarImpl, X >
{
private:
// implementation
friend class BarInterface< ::BarImpl, X >;
void do_bar() { std::cout << X << "/n"; }
};
Así es como los llamas:
int main()
{
FooImpl f1;
AnotherFooImpl f2;
BarImpl< 1 > b1;
BarImpl< 2 > b2;
f1.foo();
f2.foo();
b1.bar();
b2.bar();
return 0;
}
Las clases en su pregunta no encajan en este patrón general. Si desea darle a Derived
algún comportamiento similar al de CRTP, entonces puede hacerlo
class Derived1
:
public CRTP< Derived1 >
{
};
template<int I>
class Derived2
:
public CRTPInt< Derived2, I >
{
};
ACTUALIZACIÓN : Basado en la discusión de https://.com/a/11571808/819272 , descubrí que la respuesta original solo se compilaba en Visual Studio 2010, pero no en gcc debido a algunas características no portátiles específicas de Microsoft. Por ejemplo, la función self()
de enable_down_cast
es un nombre dependiente (plantilla) en sus clases derivadas, y por lo tanto no es visible sin directivas explícitas. Además, he agregado destructores con el nivel correcto de protección. Finalmente, he cambiado el nombre de mi clase original enable_crtp
por enable_down_cast
porque eso es precisamente lo que hace: habilitar manualmente para el polimorfismo estático lo que el compilador hace automáticamente para el polimorfismo dinámico.