Jak pisać ciekawie powtarzające się szablony z więcej niż 2 warstwami dziedziczenia?

Cały materiał, który przeczytałem o ciekawie powtarzającym się wzorze szablonów, wydaje się być jedną warstwą dziedziczenia, tj. Base i Derived : Base<Derived>. A jeśli chcę pójść o krok dalej?

#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;
}

Jak mogę przepisać ten kod do kompilacji bez błędów i wyświetlania

C
B

Używanie c. get ().print () i B. get ().print ()?

motywacja: przypuśćmy, że mam trzy klasy,

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

Każda klasa przyjmuje 6 lub więcej parametrów w swoich konstruktor, z których wiele jest opcjonalnych i ma rozsądne wartości domyślne. Aby uniknąć nudy opcjonalnych parametrów, najlepszym rozwiązaniem jest użycie idiomu nazwanego parametru.

Podstawowym problemem tego idiomu jest to, że funkcje klasy parametru muszą zwracać obiekt, na którym są wywoływane, jednak niektóre parametry są przekazywane do GuiElement, niektóre Do Window, a inne do AlertBox. Potrzebujesz sposobu na napisanie tego:

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

Jednak to prawdopodobnie nie, ponieważ, na przykład, GuiElementParameter(int) prawdopodobnie zwraca GuiElementOptions&, który nie ma funkcji WindowParameter (int).

To zostałozapytane wcześniej, a rozwiązaniem wydaje się być jakiś smak ciekawie powtarzającego się wzorca szablonów. Szczególny smak, którego używam, to tutaj .

To dużo kodu do napisania za każdym razem, gdy tworzę nowy Element Gui. Szukałem sposobów, by to uprościć. Podstawową przyczyną złożoności jest fakt, że używam CRTP do rozwiązania problemu Named-Parameter-Idiom, ale mam trzy warstwy, a nie dwie (GuiElement, Window i AlertBox), a moje obecne obejście czterokrotnie zwiększa liczbę klas, które mam. (!) Na przykład Window, WindowOptions, WindowBuilderT i WindowBuilder.

To sprowadza mnie do mojego pytania, w którym zasadniczo szukam bardziej eleganckiego sposobu korzystania z CRTP na długich łańcuchach dziedziczenia, takich jak GuiElement, Window i Alertbox.

Author: Community, 2010-05-12

3 answers

Nie jestem do końca pewna, co chcesz osiągnąć, ale to jest przybliżenie tego, o co prosisz.

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;
}

Zmieniłem wyjście, aby lepiej zilustrować dziedziczenie. W oryginalnym kodzie nie możesz udawać, że {[2] }nie jest szablonem [najlepsze na co możesz liczyć to B<>], więc coś takiego jest chyba najmniej kludnym sposobem postępowania z nim.


Z twojej drugiej odpowiedzi, (2) nie jest możliwe. Można pominąć parametry szablonu dla funkcje, jeśli argumenty funkcji są wystarczające, aby je wywnioskować, ale w przypadku klas musisz coś podać. (1) można to zrobić, ale jest niezręczne. Pomijając wszystkie różne warstwy:

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 */ };
Musisz zrobić coś podobnego na każdym poziomie. Jak mówiłem, niezręcznie. Nie możesz po prostu powiedzieć typename Derived = DefaultTag<B> > lub czegoś podobnego, ponieważ B jeszcze nie istnieje.
 9
Author: Dennis Zickefoose,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-12 10:55:41

Oto, co ustaliłem, używając wariacji na temat CRTP, aby rozwiązać problem przedstawiony w moim przykładzie motywacji. Prawdopodobnie najlepiej czytać zaczynając od dołu i przewijając w górę..

#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;
};
 4
Author: Kyle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-15 21:05:04

Myślę, że niemożliwe jest zaimplementowanie jakiegoś ogólnego mechanizmu. Musisz podać dokładny parametr szablonu za każdym razem, gdy dziedziczysz klasę bazową, bez względu na to, ile poziomów jest między nimi (sądząc po twojej odpowiedzi: teraz są 2 poziomy: nie przekazujesz C bezpośrednio do bazy, ale c owinięte w strukturę znacznika, wygląda jak wąż, który gryzie własny ogon)

Prawdopodobnie byłoby lepiej dla Twojego zadania użyć kasowania typu, a nie kuriozalnie powtarzającego się wzór szablonu. Może być, to będzie przydatne

 1
Author: Alsk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:53:16