如何编写具有 2 层以上继承的奇怪的重复模板?

发布于 2024-08-31 22:54:34 字数 2481 浏览 13 评论 0原文

我读过的关于 Curiously Recurring Template Pattern 的所有材料似乎都是一层继承,即 BaseDerived : Base。如果我想更进一步怎么办?

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

我怎样才能重写这段代码以无错误地编译并显示

C

使用 c.get().print() 和 b.get().print() ?

动机:假设我有三个类,

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

每个类在其构造函数中接受 6 个左右的参数,其中许多参数是可选的并且具有合理的默认值。要避免繁琐的可选参数,最好的解决方案是使用命名参数习惯用法

这种习惯用法的一个基本问题是,参数类的函数必须返回它们所调用的对象,但有些参数被赋予 GuiElement,一些参数被赋予 Window,一些参数被赋予 AlertBox。您需要一种方法来编写此代码:

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

但这可能会失败,因为例如 GuiElementParameter(int) 可能返回 GuiElementOptions&,它没有 WindowParameter(int) 函数。

之前已经问过这个问题,解决方案似乎是奇怪的重复模板模式的一些风格。我使用的特定风格是这里

不过,每次创建新的 Gui 元素时都要编写大量代码。我一直在寻找简化它的方法。复杂性的主要原因是我使用 CRTP 来解决 Named-Parameter-Idiom 问题,但我有三层而不是两层(GuiElement、Window 和 AlertBox)和我的 当前的解决方法使我拥有的类数量增加了四倍。 (!)例如,Window、WindowOptions、WindowBuilderT 和 WindowBuilder。

这引出了我的问题,其中我本质上是在寻找一种更优雅的方式来在长继承链(例如 GuiElement、Window 和 Alertbox)上使用 CRTP。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

杯别 2024-09-07 22:54:34

我不完全清楚您希望完成什么,但这与您似乎要求的非常接近。

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

我更改了输出以更好地说明继承。在您的原始代码中,您不能假装 B 不是模板 [您希望最好的是 B<>],所以类似这样的东西可能是最不笨拙的处理方式。


从您的其他答案来看,(2)是不可能的。如果函数的参数足以推断它们,您可以省略函数的模板参数,但对于类,您必须提供一些东西。 (1)可以做到,但比较尴尬。抛开所有不同的层次:

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

你必须在每个层次上做类似的事情。就像我说的,尴尬。您不能简单地说 typename Derived = DefaultTag; > 或类似的内容,因为 B 尚不存在。

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.

林空鹿饮溪 2024-09-07 22:54:34

这是我已经决定的,使用 CRTP 的变体来解决我的动机示例中提出的问题。可能最好从底部开始向上滚动阅读。

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

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;
};
半葬歌 2024-09-07 22:54:34

我认为实现某种通用机制是不可能的。每次继承基类时,您都必须显式指定确切的模板参数,无论之间有多少级间接(根据您的答案判断:现在有 2 级:您不直接将 C 传递给基类,但是C 包裹在标签结构中,它看起来像一条咬自己尾巴的蛇)

也许,对于您的任务来说,使用类型擦除而不是奇怪的重复模板模式会更好。可能这个会有用

I think it's impossible to implement some generic mechanism. You have to specify explicitly the exact template parameter every time you inherit base class, no matter how many levels of indirection are placed between (judging by your answer: now there are 2 levels: you don't pass C directly to the base, but C wrapped in a tag struct, it looks like a snake which bites its own tail)

Probably, it would be better for your task to use type erasure, and not curiously recurring template pattern. May be, this will be useful

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文