【问题标题】:C++ template subclass and multiple inheritance ambiguityC++模板子类和多重继承歧义
【发布时间】:2021-02-03 17:16:38
【问题描述】:

我有两个基类AB,以及第三个类C(实际上)从这两个类派生而来。每个类都有自己的公共shared_ptr 类型。

在另一个类中,我有两个向量,我想将 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

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&amp;)Test::add(const B::shared_ptr&amp;),因为实际上B 版本的add 具有解决过载的附加参数,但我更喜欢这样调用者不必记住这样做。

这种歧义可以解决吗?我的目标环境限制我使用 C++14。

【问题讨论】:

  • 请发布真实代码。 cls::add(const A::shared_ptr&amp; o) 缺少返回类型,所有别名都是私有的。
  • 您能否在edit 这个问题中包含一个出现此问题的minimal reproducible example 程序?
  • 我没有让它失败:/ godbolt.org/z/sK61Tr
  • 在我的机器上工作。我认为错误在于未提供的代码中。
  • 值得注意的是,这不是原始指针的问题 (wandbox.org/permlink/u12WIaINyE3fsIUa)。标准派生到碱基的转换序列根据继承链的长度进行排名。不幸的是,智能指针不能轻易模仿。

标签: c++ templates inheritance c++14


【解决方案1】:

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

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

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

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

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

对于任何0 &lt;= i &lt; j &lt;= krank&lt;k&gt; -&gt; rank&lt;j&gt; 的转换顺序将始终被视为优于rank&lt;k&gt; -&gt; rank&lt;i&gt;。因此,如果我们使您的重载集不可访问,并明确对其进行排名:

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&lt;10&gt; 的派生到基础的转换将影响最佳的选择。

Here it is live.

【讨论】:

  • 10 可以是任意数字&gt;=1,对吧?或者有什么原因你没有把它设为默认,return add(0,rank&lt;&gt;{});
  • 可以是任意数字&gt;=1(嗯,不超过实现的递归模板实例化限制)。如果使用rank&lt;2...10&gt; 将更多的重载添加到集合中,我只是使用一个任意的“体面”数字来为自己节省一些工作(不过,有多少重载太多了!?)。可能有更聪明的方法来处理它(反射 TS!?),但我还没有弄清楚。
  • 好的,当您添加等级为11...20 的重载时,您只需更改类定义而不是rank 模板。
  • @largest_prime_is_463035818 - 是的,完全正确。根据个人喜好,rank 可能是从一个常见的元编程头文件中提取的实用程序,而不是临时编写的。因此,默认设置可能根本不合适。
  • 谢谢 - 令人印象深刻的工作!我已经更新了我的代码以使用它并且它可以工作。现在我只需要真正正确地摸索它......
猜你喜欢
  • 1970-01-01
  • 2020-08-12
  • 2021-06-29
  • 2016-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多