代码之家  ›  专栏  ›  技术社区  ›  gct

用C++模板指定协变/逆变行为

  •  0
  • gct  · 技术社区  · 7 年前

    我看了scala中的parboiled2,它有一种有趣的方法,可以使用协变/逆变子类型对类型系统中的解析器行为进行编码:

    https://github.com/sirthias/parboiled2#rule-types-and-the-value-stack

    我很好奇,如果类似的东西可以用模板元编程在C++中完成。

    我怀疑协变行为可以用继承来模拟,但是逆变呢?

    1 回复  |  直到 7 年前
        1
  •  2
  •   hlt    7 年前

    在C++中,类似于其他OO语言,子类型通常用继承来表示。

    但是,我们不能通过继承来建模协方差和反方差关系,因为没有办法列出一个类的基类(没有任何不同的反射建议,这些建议还没有进入到语言中)。

    允许这种行为的最简单方法是允许协变和逆变模板类根据相关类型的关系进行转换。

    协方差

    如果 Derived IS-A Base 然后 Covariant<Derived> “应该是-a” Covariant<Base> .

    通常,解决办法是 协变派生 继承自 协变基 但是我们目前没有办法找到 基地 仅给出 派生 . 但是,我们 可以 通过编写的构造函数来启用转换 协变基 拿走任何 协变派生 :

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

    反差

    如果 派生 IS-A 基地 然后 Contravariant<Base> “应该是-a” 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 链接以显示其工作状态