Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write curiously recurring templates with more than 2 layers of inheritance?

Tags:

All the material I've read on Curiously Recurring Template Pattern seems to one layer of inheritance, ie Base and Derived : Base<Derived>. What if I want to take it one step further?

#include <iostream>
using std::cout;


template<typename LowestDerivedClass> class A {
public:
    LowestDerivedClass& get() { return *static_cast<LowestDerivedClass*>(this); }
    void print() { cout << "A\n"; }
};
template<typename LowestDerivedClass> class B : public A<LowestDerivedClass> {
    public: void print() { cout << "B\n"; }
};
class C : public B<C> {
    public: void print() { cout << "C\n"; }
};

int main()
{
    C c;
    c.get().print();

//  B b;             // Intentionally bad syntax, 
//  b.get().print(); // to demonstrate what I'm trying to accomplish

    return 0;
}

How can I rewrite this code to compile without errors and display

C
B

Using c.get().print() and b.get().print() ?

Motivation: Suppose I have three classes,

class GuiElement { /* ... */ };
class Window : public GuiElement { /* ... */ };
class AlertBox : public Window { /* ... */ };

Each class takes 6 or so parameters in their constructor, many of which are optional and have reasonable default values. To avoid the tedium of optional parameters, the best solution is to use the Named Parameter Idiom.

A fundamental problem with this idiom is that the functions of the parameter class have to return the object they're called on, yet some parameters are given to GuiElement, some to Window, and some to AlertBox. You need a way to write this:

AlertBox box = AlertBoxOptions()
    .GuiElementParameter(1)
    .WindowParameter(2)
    .AlertBoxParameter(3)
    .create();

Yet this would probably fail because, for example, GuiElementParameter(int) probably returns GuiElementOptions&, which doesn't have a WindowParameter(int) function.

This has been asked before, and the solution seems to be some flavor of the Curiously Recurring Template Pattern. The particular flavor I use is here.

It's a lot of code to write every time I create a new Gui Element though. I've been looking for ways to simplify it. A primary cause of complexity is the fact that I'm using CRTP to solve the Named-Parameter-Idiom problem, but I have three layers not two (GuiElement, Window, and AlertBox) and my current workaround quadruples the number of classes I have. (!) For example, Window, WindowOptions, WindowBuilderT, and WindowBuilder.

That brings me to my question, wherein I'm essentially looking for a more elegant way to use CRTP on long chains of inheritance, such as GuiElement, Window, and Alertbox.

like image 208
Kyle Avatar asked May 12 '10 17:05

Kyle


2 Answers

I'm not entirely clear on what you're hoping to accomplish, but this is a close approximation of what you seem to be asking for.

template <typename LowestDerivedClass> class A {
public:
  LowestDerivedClass &get() {
    return *static_cast<LowestDerivedClass *>(this); 
  }
  void print() {
    cout << "A"; 
  }
};

template <typename LowestDerivedClass>
class Bbase : public A<LowestDerivedClass> {
public:
  void print() {
    cout << "B";
    this->A<LowestDerivedClass>::print();
  }
};

class B : public Bbase<B> {};

class C : public Bbase<C> {
public:
  void print() {
    cout << "C";
    this->Bbase<C>::print();
  }
};

int main() {
  C c;
  c.print();
  cout << endl;
  B b;
  b.print();
  cout << endl;
}

I changed the output to illustrate the inheritance better. In your original code, you can't pretend B isn't a template [the best you could hope for is B<>], so something like this is probably the least kludgy way of handling it.


From your other answer, (2) is not possible. You can leave off template parameters for functions, if the function's arguments are sufficient to infer them, but with classes you have to provide something. (1) can be done, but its awkward. Leaving off all the various layers:

template<typename T> struct DefaultTag { typedef T type; };
template<typename Derived = void>
class B : public A<Derived> { /* what B should do when inherited from */ };
template<>
class B<void> : public A<DefaultTag<B<void> > > { /* what B should do otherwise */ };

You have to do something similar at each level. Like I said, awkward. You can't simply say typename Derived = DefaultTag<B> > or something similar because B doesn't exist yet.

like image 155
Dennis Zickefoose Avatar answered Jan 04 '23 05:01

Dennis Zickefoose


Here is what I've settled on, using a variation on CRTP's to solve the problem presented in my motivation example. Probably best read starting at the bottom and scrolling up..

#include "boost/smart_ptr.hpp"
using namespace boost;

// *** First, the groundwork....
//     throw this code in a deep, dark place and never look at it again
//
//     (scroll down for usage example)

#define DefineBuilder(TYPE, BASE_TYPE) \
    template<typename TargetType, typename ReturnType> \
    class TemplatedBuilder<TYPE, TargetType, ReturnType> : public TemplatedBuilder<BASE_TYPE, TargetType, ReturnType> \
    { \
    protected: \
        TemplatedBuilder() {} \
    public: \
        Returns<ReturnType>::me; \
        Builds<TargetType>::options; \

template<typename TargetType>
class Builds
{
public:
    shared_ptr<TargetType> create() {
        shared_ptr<TargetType> target(new TargetType(options));
        return target;
    }

protected:
    Builds() {}
    typename TargetType::Options options;
};

template<typename ReturnType>
class Returns
{
protected:
    Returns() {}
    ReturnType& me() { return *static_cast<ReturnType*>(this); }
};

template<typename Tag, typename TargetType, typename ReturnType> class TemplatedBuilder;
template<typename TargetType> class Builder : public TemplatedBuilder<TargetType, TargetType, Builder<TargetType> > {};

struct InheritsNothing {};
template<typename TargetType, typename ReturnType>
class TemplatedBuilder<InheritsNothing, TargetType, ReturnType> : public Builds<TargetType>, public Returns<ReturnType>
{
protected:
    TemplatedBuilder() {}
};

// *** preparation for multiple layer CRTP example *** //
//     (keep scrolling...)

class A            
{ 
public: 
    struct Options { int a1; char a2; }; 

protected:
    A(Options& o) : a1(o.a1), a2(o.a2) {}
    friend class Builds<A>;

    int a1; char a2; 
};

class B : public A 
{ 
public: 
    struct Options : public A::Options { int b1; char b2; }; 

protected:
    B(Options& o) : A(o), b1(o.b1), b2(o.b2) {}
    friend class Builds<B>;

    int b1; char b2; 
};

class C : public B 
{ 

public: 
    struct Options : public B::Options { int c1; char c2; };

private:
    C(Options& o) : B(o), c1(o.c1), c2(o.c2) {}
    friend class Builds<C>;

    int c1; char c2; 
};


// *** many layer CRTP example *** //

DefineBuilder(A, InheritsNothing)
    ReturnType& a1(int i) { options.a1 = i; return me(); }
    ReturnType& a2(char c) { options.a2 = c; return me(); }
};

DefineBuilder(B, A)
    ReturnType& b1(int i) { options.b1 = i; return me(); }
    ReturnType& b2(char c) { options.b2 = c; return me(); }
};

DefineBuilder(C, B)
    ReturnType& c1(int i) { options.c1 = i; return me(); }
    ReturnType& c2(char c) { options.c2 = c; return me(); }
};

// note that I could go on forever like this, 
// i.e. with DefineBuilder(D, C), and so on.
//
// ReturnType will always be the first parameter passed to DefineBuilder.
// ie, in 'DefineBuilder(C, B)', ReturnType will be C.

// *** and finally, using many layer CRTP builders to construct objects ***/

int main()
{
    shared_ptr<A> a = Builder<A>().a1(1).a2('x').create();
    shared_ptr<B> b = Builder<B>().a1(1).b1(2).a2('x').b2('y').create();
    shared_ptr<B> c = Builder<C>().c2('z').a1(1).b1(2).a2('x').c1(3).b2('y').create(); 
    // (note: any order works)

    return 0;
};
like image 26
Kyle Avatar answered Jan 04 '23 05:01

Kyle