我正在研究 Scala 中的 parboiled2,它有一种有趣的方式来使用协变/逆变子类型在类型系统中编码解析器行为:
https://github.com/sirthias/parboiled2#rule-types-and-the-value-stack
我很好奇是否可以使用模板元编程在 C++ 中完成类似的事情。
我怀疑协变行为可以通过继承来模拟,但是逆变呢?
最佳答案
在 C++ 中,与在其他 OO 语言中一样,子类型通常由继承表示。
但是,我们无法通过继承对协变和逆变关系进行建模,因为无法列出类的基类(没有任何各种反射提议,这些提议尚未进入语言)。
允许这种行为的最简单方法是允许协变和逆变模板类根据相关类型的关系进行转换。
协方差
If
Derived
is-aBase
, thenCovariant<Derived>
"should-be-a"Covariant<Base>
.
通常,解决方案是制作Covariant<Derived>
继承自 Covariant<Base>
, 但我们目前无法找到 Base
只给出Derived
.但是,我们可以通过为 Covariant<Base>
编写构造函数来启用转换。采取任何Covariant<Derived>
:
template <typename T>
struct Covariant {
template <typename Derived>
Covariant(const Covariant<Derived>& derived,
std::enable_if_t<std::is_base_of_v<T, Derived>>* = nullptr)
{
/* Do your conversion here */
}
};
逆变
If
Derived
is-aBase
, thenContravariant<Base>
"should-be-a"Contravariant<Derived>
这里的技巧几乎相同 - 允许转换任何 Contravariant<Base>
至 Contravariant<Derived>
:
template <typename T>
struct Contravariant {
template <typename Base>
Contravariant(const Contravariant<Base>& base,
std::enable_if_t<std::is_base_of_v<Base, T>>* = nullptr)
{
/* Do your conversion here */
}
};
然而
这有一个主要缺点:您需要手动实现转换,并注意意外的 object slicing可能会破坏你转换回来的能力(例如,如果你定义了一个协变容器类型,那将是一个令人头疼的问题)。
本质上,在反射允许我们自动化那种继承关系之前,转换是唯一的方法,我不建议将它用于任何复杂的事情。一旦您存储了 T
的对象在协变/逆变类中,你将陷入一个痛苦的世界。
这是一个 Godbolt显示它有效的链接
关于c++ - 使用 C++ 模板指定协变/逆变行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50427648/