[英]C++ template subclass and multiple inheritance ambiguity
我有两个基类A
和B
,以及第三个 class C
(实际上)从它们两者派生。 每个 class 都公开了自己的公共shared_ptr
类型。
在另一个 class 我有两个向量,我想将 A 类型的对象添加到一个向量,将 B 类型的对象添加到另一个向量,并将 C 类型的对象添加到两者。 这导致了三个add
方法,一个用于这三个类中的每一个。
当我尝试从C
进一步派生时,我的问题出现了:
#include <iostream>
#include <memory>
#include <vector>
class A {
public:
using shared_ptr = std::shared_ptr<A>;
virtual ~A() {};
};
class B {
public:
using shared_ptr = std::shared_ptr<B>;
virtual ~B() {};
};
class C : virtual public A, virtual public B {
public:
using shared_ptr = std::shared_ptr<C>;
virtual ~C() {};
};
class D : virtual public C {
public:
virtual ~D() {};
};
class Test {
protected:
std::vector<A::shared_ptr> vecA;
std::vector<B::shared_ptr> vecB;
public:
void add(const A::shared_ptr& o) {
std::cerr << "in A" << std::endl;
vecA.push_back(o);
}
void add(const B::shared_ptr& o) {
std::cerr << "in B" << std::endl;
vecB.push_back(o);
}
void add(const C::shared_ptr& o) {
std::cerr << "in C" << std::endl;
vecA.push_back(o);
vecB.push_back(o);
}
};
int main()
{
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
auto c = std::make_shared<C>();
auto d = std::make_shared<D>();
Test t;
t.add(a);
t.add(b);
t.add(c);
t.add(d);
}
这不起作用 - 无法确定哪个版本的add
to call 的分辨率:
test.cc:62:7: error: call to member function 'add' is ambiguous
t.add(d);
~~^~~
test.cc:34:10: note: candidate function
void add(const A::shared_ptr& o) {
^
test.cc:39:10: note: candidate function
void add(const B::shared_ptr& o) {
^
test.cc:44:10: note: candidate function
void add(const C::shared_ptr& o) {
^
我确实可以选择简单地将我的C
object 分别传递给Test::add(const A::shared_ptr&)
和Test::add(const B::shared_ptr&)
因为实际上B
版本的add
具有解析的附加参数重载,但我希望调用者不必记住这样做。
这种歧义可以解决吗? 我的目标环境将我限制在 C++14。
在对转换序列进行排序时,标准派生到碱基的转换序列会考虑 inheritance 链的长度,一个接近的碱基将被认为比 inheritance 链更靠前的转换序列更好。 这反过来也会影响指针和引用!
遗憾的是,由于智能指针是用户定义的类型,它们无法从这种行为中受益。 通过(有效的)用户定义的转换,所有三个重载都是可行的。 并且各个基地的“排名”不会影响过载的排名。
但这并不意味着我们不能重新引入派生到基础转换强加的排名。 我们只需要通过另一个参数来做到这一点。 通过使用标签调度,我们可以做到这一点。
我们可以定义一个辅助实用程序类型:
template<int n> struct rank : rank<n - 1> {};
template<> struct rank<0> {};
对于任何0 <= i < j <= k
, rank<k> -> rank<j>
的转换顺序总是被认为比rank<k> -> rank<i>
更好。 因此,如果我们使您的重载集不可访问,并明确对它们进行排名:
protected:
void add(const A::shared_ptr& o, rank<0>) { /*...*/ }
void add(const B::shared_ptr& o, rank<0>) { /*...*/ }
void add(const C::shared_ptr& o, rank<1>) { /*...*/ }
然后,我们可以以 function 模板的形式公开另一个重载:
public:
template<typename T>
void add(const std::shared_ptr<T>& o) {
return add(o, rank<10>{});
}
它主要只是转发到一个受保护的重载,但它添加了另一个参数。 等级标签。 这也会影响重载分辨率。 尽管所有三个add
重载都是可行的,但rank<10>
的派生到基数的转换将影响最佳的选择。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.