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 {}; };
这将使 Wrapper
s 遵循与它们包装的类型相同的继承树。
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. 之后制作此类工作的示例
我有一个 "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 {}; };
这将使 Wrapper
s 遵循与它们包装的类型相同的继承树。
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. 之后制作此类工作的示例