Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't my Curiously Recurring Template Pattern (CRTP) refer to the derived class's typedefs? [duplicate]

When using the curiously recurring template pattern, I am unable to refer to typedefs belonging to the derived class only if I attempt to reference them from the base class; gcc complains no type named 'myType' in class Derived<...>. This seems inconsistent with what is otherwise possible using typedefs, templates, and curiously recurring relationships.

Consider:

/* crtp.cpp */

#include <iostream>
using namespace std;

// case 1. simple.

class Base {
public:
    typedef int a_t;

    a_t foo;
};

class Derived : public Base {
    a_t bar;
};

// case 2. template.

template<typename T>
class tBase {
public:
    typedef T b_t;
    T foo;
};

template <typename T>
class tDerived : public tBase<T> {
    typename tBase<T>::b_t bar;
};

// case 3. curiously recurring.

template <typename T, typename D>
class tCuriousBase {
public:
    typedef T c_t;
    c_t foo;
};

template <typename T>
class tCuriousDerived : public tCuriousBase<T,tCuriousDerived<T> > {
    typename tCuriousBase<T,tCuriousDerived<T> >::c_t bar;
};

// case 4. curiously recurring with member reference.

template <typename T, typename D>
class tCuriousMemberBase {
public:
    T foo;

    T get() {
        return static_cast<D*>(this)->bar;
    }
};

template <typename T>
class tCuriousMemberDerived : public tCuriousMemberBase<T, tCuriousMemberDerived<T> > {
public:
    T bar;

    tCuriousMemberDerived(T val) : bar(val) {}
};

// case 5. curiously recurring with typedef reference.

template <typename T, typename D>
class tCuriousTypeBase {
public:
    typedef T d_t;
    d_t foo;
    typename D::c_t baz;
};

template <typename T>
class tCuriousTypeDerived : public tCuriousTypeBase<T, tCuriousTypeDerived<T> > {
public:
    typedef T c_t;
    typename tCuriousTypeBase<T,tCuriousTypeDerived<T> >::d_t bar;
};

// entry point

int main(int argc, char **argv) {
    Derived::a_t one = 1;
    tDerived<double>::b_t two = 2;
    tCuriousDerived<double>::c_t three = 3;
    double four = tCuriousMemberDerived<double>(4).get();
    tCuriousTypeBase<double, tCuriousDerived<double> >::d_t five = 5;
    // tCuriousTypeDerived<double>::d_t six = 6; /* FAILS */

    cout << one   << endl;
    cout << two   << endl; 
    cout << three << endl;
    cout << four  << endl;
    cout << five  << endl;
    // cout << six << endl;
}

From (1), we see that typedefs are indeed inherited from base to derived; a typedef declared in the base class can be accessed through the derived class.

From (2), we see that this is still true if both classes are templates.

From (3), we see that this typedef inheritance can still exist in the presence of a curiously recurring template relationship; we refer to the base's typedef via the derived class in our declaration of three.

From (4), we see that member variables of the derived class may be readily accessed from the base class.

From (5), we see that we can access a typedef defined on a template parameter (this works when we declare five using types that are not related by inheritance).

As soon as we make the template parameter a derived class in (6), however, suddenly the typedef becomes inaccessible, even though it is seemingly as well-defined as any member variable in the derived class.

Why is this?

like image 320
trbabb Avatar asked Apr 19 '13 06:04

trbabb


1 Answers

This is the "circular dependecies", or "incomplete-type" alter-ego.

Template "meta-programming" is "programming types", but it requires a certain level of sematics to be known to properly instantiate types.

Consider this analogy:

class A; //forwarded

class B
{
   A* pa; //good: we know how wide a pointer is, no matter if we don't know yet anything about A.
   A a; // bad: we don't know how much space it requires
};

class A
{
  float m;
}; // now we know, but it's too late

This can be solved by placing A before B, but if A is

class A
{
   B m;
};

Thhere is no other solution than pointers, since A recursion will be infinite. (A should contain itself, not refer to another copy)

Now, with the same analogy, let's program "types":

template<class D>
class A
{
   typedef typename D::inner_type my_type; //required D to be known when A<D> is instantiated...
   my_type m; // ... otherwise we cannot know how wide A<D> will be.
};

This declaration is not itself bad,until we start to define D as ...

class D: //here we only know D exist
    public A<D> //but A size depende on D definition...
{
  ....
  typedef long double; inner_type
  ....
}; // ....we know only starting from here

So, basically, we don't know (yet) how wide is A at the time need to use it to create D.

One way to break this "circularity" is to use some "traits classes" outside of the CRT loop:

struct traits
{
   typedef long double inner_type;
   ....
};

template<class D, class Traits>
class A
{
  // this is easy: Traits does not depend itself on A
  typedef typename Traits::inner_type my_type;
  ....
};

template<class Traits>
class D: public A<D, Traits>
{
  typedef typename Traits::inner_type inner_type;
};

An we can finally declare

typedef D<traits> D_Inst;

In other words, the coherence between A::my_type and D::inner_type is ensured by traits::inner_type, whose definition is independent.

like image 110
Emilio Garavaglia Avatar answered Sep 24 '22 01:09

Emilio Garavaglia