如何编写具有 2 层以上继承的奇怪的重复模板?
我读过的关于 Curiously Recurring Template Pattern 的所有材料似乎都是一层继承,即 Base
和 Derived : 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我不完全清楚您希望完成什么,但这与您似乎要求的非常接近。
我更改了输出以更好地说明继承。在您的原始代码中,您不能假装
B
不是模板 [您希望最好的是B<>
],所以类似这样的东西可能是最不笨拙的处理方式。从您的其他答案来看,(2)是不可能的。如果函数的参数足以推断它们,您可以省略函数的模板参数,但对于类,您必须提供一些东西。 (1)可以做到,但比较尴尬。抛开所有不同的层次:
你必须在每个层次上做类似的事情。就像我说的,尴尬。您不能简单地说
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.
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 isB<>
], 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:
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 becauseB
doesn't exist yet.这是我已经决定的,使用 CRTP 的变体来解决我的动机示例中提出的问题。可能最好从底部开始向上滚动阅读。
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..
我认为实现某种通用机制是不可能的。每次继承基类时,您都必须显式指定确切的模板参数,无论之间有多少级间接(根据您的答案判断:现在有 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