C++模板子类和多重继承歧义

C++ template subclass and multiple inheritance ambiguity

我有两个基础 classes AB,还有第三个 class C (实际上)来自它们两个.每个 class 公开其自己的 public shared_ptr 类型。

在另一个 class 中,我有两个向量,我想将 A 类型的对象添加到一个向量,将 B 类型的对象添加到另一个向量,并将 C 类型的对象添加到两者。这导致三种 add 方法,这三种 classes.

中的每一种方法。

当我尝试进一步从 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&),因为实际上 addB 版本有额外的参数来解决重载,但我希望调用者不必记住这样做。

这个歧义可以解决吗?我的目标环境将我限制为 C++14。

标准的衍生到碱基的转化序列在对转化序列进行排序时会考虑继承链的长度,相近的碱基将被视为比继承链上游的碱基更好的转化序列。这反过来也会影响指针和引用!

遗憾的是,由于智能指针是用户定义的类型,因此它们无法从这种行为中获益。通过(有效的)用户定义的转换,所有三个重载都是可行的。并且各个碱基的“排名”不影响过载的排名。

但这并不意味着我们不能通过派生到基础的转换重新引入排名强加。我们只需要通过另一个参数来做到这一点。通过使用标签分发,我们可以做到这一点。

我们可以定义一个辅助工具类型:

template<int n> struct rank : rank<n - 1> {};
template<>      struct rank<0> {};

对于任何 0 <= i < j <= krank<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>{});
    }

它主要只是转发到一个受保护的重载,但它添加了另一个参数。等级标签。这也会影响过载决议。尽管所有三个 add 重载都是可行的,rank<10> 的派生到基础的转换将影响最佳选择。

Here it is live.