I try to use the Curiously Recurring Template Pattern (CRTP) and provide additional type parameters:
template <typename Subclass, typename Int, typename Float>
class Base {
Int *i;
Float *f;
};
...
class A : public Base<A, double, int> {
};
This is probably a bug, the more appropriate superclass would be Base<A, double, int>
-- although this argument order mismatch is not so obvious to spot. This bug would be easier to see if I could use name the meaning of the parameters in a typedef:
template <typename Subclass>
class Base {
typename Subclass::Int_t *i; // error: invalid use of incomplete type ‘class A’
typename Subclass::Float_t *f;
};
class A : public Base<A> {
typedef double Int_t; // error: forward declaration of ‘class A’
typedef int Double_t;
};
However, this does not compile on gcc 4.4, the reported errors are given as comments above -- I think the reason is that before creating A, it needs to instantiate the Base template, but this in turn would need to know A.
Is there a good way of passing in "named" template parameters while using CRTP?
CRTP may be used to implement "compile-time polymorphism", when a base class exposes an interface, and derived classes implement such interface.
It's called a cardiac resynchronization therapy pacemaker (CRT-P) or “biventricular pacemaker.” The other is the same device, but it also includes a built-in implantable cardioverter defibrillator (ICD). This type is called a cardiac resynchronization therapy defibrillator (CRT-D).
CRTP is faster because there is no virtual function call overhead and it compiles to smaller code because no type information is generated.
You can use a traits class:
// Must be specialized for any type used as TDerived in Base<TDerived>.
// Each specialization must provide an IntType typedef and a FloatType typedef.
template <typename TDerived>
struct BaseTraits;
template <typename TDerived>
struct Base
{
typename BaseTraits<TDerived>::IntType *i;
typename BaseTraits<TDerived>::FloatType *f;
};
struct Derived;
template <>
struct BaseTraits<Derived>
{
typedef int IntType;
typedef float FloatType;
};
struct Derived : Base<Derived>
{
};
@James answer is obviously right, but you could still have some issues nonetheless, if the user does not provide correct typedefs.
It is possible to "assert" that the types used are right using compile-time checking facilities. Depending on the version of C++ you use, you could have to use Boost.
In C++0x, this is done combining:
static_assert
: a new facility for compile-time checking, which let's you specify a messagetype_traits
header, which provides some predicates like std::is_integral
or std::is_floating_point
Example:
template <typename TDerived>
struct Base
{
typedef typename BaseTraits<TDerived>::IntType IntType;
typedef typename BaseTraits<TDerived>::FloatType FloatType;
static_assert(std::is_integral<IntType>::value,
"BaseTraits<TDerived>::IntType should have been an integral type");
static_assert(std::is_floating_point<FloatType>::value,
"BaseTraits<TDerived>::FloatType should have been a floating point type");
};
This is very similar to typical Defensive Programming idioms in the runtime world.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With