13

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?

hrr
  • 1,807
  • 2
  • 21
  • 35
  • 3
    just for confirmation, what you think is right :) It is indeed because your proposal would make for an infinite recursion when trying to instantiate `A`. – Matthieu M. Apr 15 '11 at 18:08
  • Possible duplicate of [C++ static polymorphism (CRTP) and using typedefs from derived classes](http://stackoverflow.com/questions/6006614/c-static-polymorphism-crtp-and-using-typedefs-from-derived-classes) – George Hilliard Aug 01 '16 at 14:23

3 Answers3

24

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 McNellis
  • 348,265
  • 75
  • 913
  • 977
  • I'm having some problems with this; I get `invalid use of incomplete type` errors when trying to access `typename BaseTraits::IntType` in the `Base` template struct. In the example here, the required specialisation `BaseTraits` is given, but in real code isn't this going to be elsewhere (as it is in my code)? So there is only the forward declaration available, so indeed the type will generally be incomplete? How do I get around this? It doesn't make sense in my code to put the specialisation in with the base declarations, because it is logically part of the derived class. – Ben Farmer Feb 19 '16 at 14:31
  • Ahh, never mind, indeed there was just a problem wherein the wrong template parameter was getting passed to BaseTraits, so indeed it was not trying to use my specialisation that was defined elsewhere. Works fine once the template parameters match properly :). – Ben Farmer Feb 19 '16 at 15:14
10

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

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
2

You actually don't even need the traits classes. The following also works:

template 
<
   typename T1, 
   typename T2, 
   template <typename, typename> class Derived_
>
class Base
{
public:
   typedef T1 TypeOne;
   typedef T2 TypeTwo;
   typedef Derived_<T1, T2> DerivedType;
};

template <typename T1, typename T2>
class Derived : public Base<T1, T2, Derived>
{
public:
   typedef Base<T1, T2, Derived> BaseType;
   // or use T1 and T2 as you need it
};

int main()
{
   typedef Derived<int, float> MyDerivedType;
   MyDerivedType Test;

   return 0;
}
Ben
  • 21
  • 2
  • Hm, this is less foolproof than the intended solution -- a bug using `Derived` instead of `Derived` would be hard to spot. – hrr Nov 01 '13 at 12:57
  • Well, you could include the static asserts Matthieu M. suggested in the previous post. It is also clearer if the name `T1` is replaced by e.g. `FloatType_` and `T2` by e.g. `IntType_` (and then `typedef IntType_ IntType;` and `typedef FloatType_ FloatType;`). Alternatively, you could use a "static if statement" that figures out if T1 is indeed an integer type, and if so, typedef T1 to IntType, if not, typedef T2 to IntType. – Ben Nov 05 '13 at 16:43