C++ 模板、多态性和模板协变

C++ Templates, Polymorphism, and Template Covariance

我有一个 "wrapper" 模板,如下所示:

template <typename T>
class Wrapper {
  virtual T* get() = 0;
}

那么我有如下的继承方案:

class A {
  // things
}

class B : public A {
  // more things
}

我有另一个使用该包装器的 class:

class Worker {
  void work(Wrapper<A>* wa){
    A* a = wa->get();
    //do stuff with a.
  }
}

所有可以用 A* 完成的,都可以用 B* 完成,但我不能将 Wrapper<B>* 作为 Wrapper<A>* 传递给 Worker。有没有办法允许这样做?

我已阅读有关 C++ 的概念,但我不知道它们如何解决问题。

不能直接做,因为C++没有协变性。在默认情况下方法不是虚拟的 C++ 中(不像 Java,我猜是 C#),不清楚这是否是一个很棒的想法(而且语言已经非常复杂)。基本上你要做的是在 work:

上有一个薄的模板包装器
class Worker {
public:
  template <class T>
  void work(Wrapper<T>* wa){
    work_on_a(wa->get());
  }
private:
  void work_on_a(A* a) { // do stuff with a}
}

这仍然是安全的,因为只有当包装类型派生自 A 或以其他方式定义到 A 的隐式转换时才会编译,否则对 work_on_a 的函数调用将失败。

你可以在这里做更复杂的事情,有各种利弊,但这使代码尽可能简单,并最大限度地减少模板膨胀。

虽然请注意,如果 work 也需要是虚拟的,这将不起作用(因为虚拟函数不能是模板),事情会很快变得毛茸茸的。

另一种方法是争论包装器根本不应该传递给 work;如果您需要做的只是一个指向 A 的指针,那么这就是您应该传入的全部内容。但是根据您的实际代码,这可能不是一个选项,或者没有意义。

如果每个 class 报告其碱基:

class A {
  using Base = void;
  // things
}

class B : public A {
  using Base = A;
  // more things
}

你可以做到

template<typename T>
class Wrapper : public EmptyForVoid<T::Base>::Type

并使用 EmptyForVoid 作为基础 class 选择:

template<typename T>
struct EmptyForVoid { using Type = T; };

template<>
struct EmptyForVoid<void> { struct Type {}; };

这将使 Wrappers 遵循与它们包装的类型相同的继承树。

Curiously Recurring Template Pattern:

一点怎么样
class SubBase {
    virtual T* get() { /* default impl. */ }
};

template <typename T>
class Base : public SubBase {};

template <typename T>
class Wrapper : public Base<T> {
    virtual T* get() = 0;
}

…我不知道你的继承图是什么样的,但只要你包装的 T 类型没有 get() 方法,这应该可以工作。


编辑:正如已经指出的那样,这个简单的片段不起作用,也不是真正的 CRTP;我只能在 fairly significant modification.

之后制作此类工作的示例