简体   繁体   中英

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. Each class exposes its own public shared_ptr type.

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. This results in three add methods, one for each of those three classes.

My problems arise when I try to further derive from 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:

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.

Is this ambiguity resolvable? My target environment constrains me to 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. 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> . 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:

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.

Here it is live .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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