简体   繁体   English

C++ 模板子类和多个 inheritance 歧义

[英]C++ template subclass and multiple inheritance ambiguity

I have two base classes A , and B , and a third class C that (virtually) derives from both of them.我有两个基类AB ,以及第三个 class C (实际上)从它们两者派生。 Each class exposes its own public shared_ptr type.每个 class 都公开了自己的公共shared_ptr类型。

In another class I have two vectors where I want to add objects of type A to one vector, objects of type B to another vector, and objects of type C to both.在另一个 class 我有两个向量,我想将 A 类型的对象添加到一个向量,将 B 类型的对象添加到另一个向量,并将 C 类型的对象添加到两者。 This results in three add methods, one for each of those three classes.这导致了三个add方法,一个用于这三个类中的每一个。

My problems arise when I try to further derive from C :当我尝试从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);
}

This doesn't work - the resolution of which version of add to call cannot be determined:这不起作用 - 无法确定哪个版本的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) {
         ^

I do have the option of simply passing my C object separately to both Test::add(const A::shared_ptr&) and Test::add(const B::shared_ptr&) because in reality the B version of add has additional parameters that resolve the overload but I would prefer that the caller not have to remember to do this.我确实可以选择简单地将我的C object 分别传递给Test::add(const A::shared_ptr&)Test::add(const B::shared_ptr&)因为实际上B版本的add具有解析的附加参数重载,但我希望调用者不必记住这样做。

Is this ambiguity resolvable?这种歧义可以解决吗? My target environment constrains me to C++14.我的目标环境将我限制在 C++14。

The standard derived-to-base conversion sequences take the length of the inheritance chain into account when ranking conversion sequences, a close base would be deemed a better conversion sequence than a one that is further up the inheritance chain.在对转换序列进行排序时,标准派生到碱基的转换序列会考虑 inheritance 链的长度,一个接近的碱基将被认为比 inheritance 链更靠前的转换序列更好。 And that in turn affects pointers and references too!这反过来也会影响指针和引用!

Sadly, since smart pointers are user defined types, they cannot benefit from this behavior.遗憾的是,由于智能指针是用户定义的类型,它们无法从这种行为中受益。 All three overloads are viable via a (valid) user defined conversion.通过(有效的)用户定义的转换,所有三个重载都是可行的。 And the "ranks" of the individual bases don't affect the ranking of the overloads.并且各个基地的“排名”不会影响过载的排名。

But that doesn't mean we can't re-introduce the ranking impose by a derived-to-base conversion.但这并不意味着我们不能重新引入派生到基础转换强加的排名。 We just need to do so via another argument.我们只需要通过另一个参数来做到这一点。 And by employing tag-dispatch, we can do just that.通过使用标签调度,我们可以做到这一点。

We can define a helper utility type:我们可以定义一个辅助实用程序类型:

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

For any 0 <= i < j <= k , the conversion sequence of rank<k> -> rank<j> will always be deemed better than rank<k> -> rank<i> .对于任何0 <= i < j <= krank<k> -> rank<j>的转换顺序总是被认为比rank<k> -> rank<i>更好。 So, if we make your overload set inaccessible, and rank them explicitly:因此,如果我们使您的重载集不可访问,并明确对它们进行排名:

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>) { /*...*/ }

We can then expose another overload in the form of a function template:然后,我们可以以 function 模板的形式公开另一个重载:

public:
    template<typename T>
    void add(const std::shared_ptr<T>& o) {
        return add(o, rank<10>{});
    }

It mainly just forwards to one of the protected overloads, but it adds another argument.它主要只是转发到一个受保护的重载,但它添加了另一个参数。 A rank tag.等级标签。 This will affect overload resolution too.这也会影响重载分辨率。 Even though all three add overloads are viable, the derived-to-base conversion of rank<10> will affect the choice of best one.尽管所有三个add重载都是可行的,但rank<10>的派生到基数的转换将影响最佳的选择。

Here it is live .这里是现场直播

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM