有一个现有的问题/答案描述了模板子类化的含义,以及它在 SeqAn 中的使用方式。让我在这里举一个更简单的例子。
// No default `Spec` given = “virtual” base class!
template <typename Spec> struct Fruit {};
template <typename Spec>
inline void eat(Fruit<Spec> const&) {
std::cout << "nibbling an unknown fruit\n";
}
请注意,模板子类化中的方法始终是自由函数,因为方法分派严格通过重载而不是像传统子类化中的覆盖来工作。
这为 s 定义了一个基类Fruit
,以及一个eat
可以在任何水果上调用的方法。
我们现在将定义专业化的层次结构(“子类”)。首先,简单的;这些只是类型标签,它们将被插入到Spec
模板参数中:
struct Orange;
struct Apple;
而已。我们甚至不需要在我们的示例中定义这些标签,声明它们就足够了(但对于更复杂的情况,可能需要定义;在 SeqAn 中,总是定义类型标签)。
eat
现在,这里是 s方法的覆盖Apple
:
template <>
inline void eat(Fruit<Apple> const&) {
std::cout << "scrunching an apple\n";
}
由于我们不覆盖Orange
s 的方法,因此它们将始终调用基类方法。
这里有一些更专业的柑橘类水果(一个子类层次结构):
struct Lemon;
struct Lime;
template <typename Spec = Lemon> struct Citrus { };
请注意,与 for Apple
s 和Orange
s 不同,Citrus
它本身是一个可以子类化的模板。eat
我们现在可以像以前一样覆盖Citrus
水果,但我想展示子类方法如何分派给基类方法;为此,让我们引入一个辅助函数模板eat_citrus
,它将从以下位置调用eat
:
template <typename Spec>
inline void eat(Fruit<Citrus<Spec>> const&) {
eat_citrus<Spec>();
}
eat_citrus
这是for any的基类定义Citrus
:
template <typename Tag = Lemon>
inline void eat_citrus() {
std::cout << "ew, that’s sour!\n";
}
这是Lime
s 的覆盖:
template <>
inline void eat_citrus<Lime>() {
std::cout << "nice taste, but ";
eat_citrus<>(); // Calls the base class method.
}
最后,如果我们使用这些类如下:
// Does not work, since `Fruit` is “virtual”:
// Fruit<> fruit;
Fruit<Orange> orange;
Fruit<Apple> apple;
Fruit<Citrus<>> lemon;
Fruit<Citrus<Lime>> lime;
eat(orange);
eat(apple);
eat(lemon);
eat(lime);
…我们得到这个输出:
nibbling an unknown fruit
scrunching an apple
ew, that’s sour!
nice taste, but ew, that’s sour!
Runnable code on Coliru
在 SeqAn 中,上述操作略有不同;为了简单起见,我在这里对其进行了更改,老实说,因为我使用 SeqAn 已经有好几年了,我不记得细节了。如果我没记错的话,eat_citrus
实际上并没有必要,标签调度 + 重载(如 Yuki 所述)将在这里使用,而不是模板专业化。
另请注意,我的代码不使用 C++ 用语中的任何实际继承(即Fruit<Apple>
不继承自Fruit<whatever>
)。这不是绝对必要的,但通常非常有用,IIRC 大多数 SeqAn 类模板实际上也继承了它们的基类。