Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Curiously Recurring Template Pattern (CRTP) with additional type parameters

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?

like image 979
hrr Avatar asked Apr 15 '11 17:04

hrr


People also ask

What is CRTP used for?

CRTP may be used to implement "compile-time polymorphism", when a base class exposes an interface, and derived classes implement such interface.

What is CRTP?

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).

Is CRTP faster?

CRTP is faster because there is no virtual function call overhead and it compiles to smaller code because no type information is generated.


2 Answers

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> 
{
};
like image 194
James McNellis Avatar answered Nov 15 '22 16:11

James McNellis


@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 message
  • the type_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.

like image 31
Matthieu M. Avatar answered Nov 15 '22 15:11

Matthieu M.