Why is my curiously repeating pattern template (CRTP) not related to derived typedefs?

When using a curiously repeating template pattern , I cannot refer to typedefs belonging to a derived class only if I try to refer to them from a base class; gcc complains no type named 'myType' in class Derived<...> . This seems incompatible with what is otherwise possible using typedefs, patterns, and curiously repeating 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; } 

It can be seen from (1) that typedefs are indeed inherited from the base to the derivative; using the typedef declared in the base class can be accessed through the derived class.

It can be seen from (2) that this is still true if both classes are templates.

It can be seen from (3) that this typedef inheritance can still exist if there is a curiously repeating pattern relation; we reference the base typedef through the derived class in our three declaration.

It can be seen from (4) that member variables of a derived class can be easily accessed from the base class.

It can be seen from (5) that we can access the typedef parameter defined in the template parameter (this works when we declare five using types that are not inherited).

As soon as we make the template parameter a derived class in (6), however, suddenly the typedef becomes unavailable, although it is apparently also defined as any member variable in the derived class.

Why is this?

+4
source share
1 answer

These are "circular dependencies" or "incomplete" alter ego.

The “meta-programming” template is “programming types”, but this requires a certain level of semantics for the correct creation of 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 too late 

This can be solved by putting A in front of B, but if A is

 class A { B m; }; 

There is no other solution than pointers, since the recursion will be infinite. (A must contain itself, not reference another copy)

Now with the same analogy , let the 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 alone is not bad, until we begin 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 yet know how wide A is at the time when you need to use it to create D.

One way to break this “roundness” is to use some “feature classes” outside 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; }; 

We can finally announce

 typedef D<traits> D_Inst; 
In other words, the coherence between A::my_type and D::inner_type provided by traits::inner_type , the definition of which is independent.
+7
source

All Articles