我有两个基类 A
和 B
,以及(实际上)从它们派生的第三个类 C
。每个类都公开其自己的公共(public) shared_ptr
类型。
在另一个类中,我有两个 vector ,我想将类型 A 的对象添加到一个 vector ,将类型 B 的对象添加到另一个 vector ,并将类型 C 的对象添加到两个 vector 。这会产生三个 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
:
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
对象分别传递给 Test::add(const A::shared_ptr&)
和 Test::add (const B::shared_ptr&)
因为实际上 add
的 B
版本具有解决重载的附加参数,但我希望调用者不必这样做请记住执行此操作。
这个歧义可以解决吗?我的目标环境将我限制为 C++14。
最佳答案
标准的派生到碱基转换序列在对转换序列进行排序时考虑了继承链的长度,接近的碱基将被视为比继承链更靠上的碱基更好的转换序列。这反过来也会影响指针和引用!
遗憾的是,由于智能指针是用户定义的类型,因此它们无法从这种行为中受益。所有三个重载都可以通过(有效的)用户定义的转换实现。并且各个碱基的“排名”不会影响重载的排名。
但这并不意味着我们不能重新引入派生到基数转换所施加的排名。我们只需要通过另一个论证来做到这一点。通过使用标签调度,我们就可以做到这一点。
我们可以定义一个辅助实用程序类型:
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>) { /*...*/ }
然后我们可以以函数模板的形式公开另一个重载:
public:
template<typename T>
void add(const std::shared_ptr<T>& o) {
return add(o, rank<10>{});
}
它主要只是转发到一个 protected 重载,但它添加了另一个参数。等级标签。这也会影响过载解析。即使这三个 add
重载是可行的, rank<10>
的派生到基数转换会影响最佳选择的选择。
关于C++ 模板子类和多重继承歧义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66032442/