【问题标题】:Resolving overloading ambiguity with multiple inheritance of base class templates in C++在 C++ 中使用基类模板的多重继承解决重载歧义
【发布时间】:2021-06-29 15:44:04
【问题描述】:

假设我正在尝试创建一个从给定基类派生的Combine 类。

template<typename ...Bases>
class Combine : public Bases... {};

这很好用。例如,如果我有FooBar 类,那么Combine&lt;Foo, Bar&gt; 类将实现FooBar 中的所有方法。至少在我尝试这个之前我是这么认为的:

struct ContainerProvider {
    std::vector<int> container{1, 2, 3};
};

struct ConstGetter : public virtual ContainerProvider {
    [[nodiscard]] const int &get(int index) const {
        return container[index];
    }
};

struct MutableGetter : public virtual ContainerProvider {
    int &get(int index) {
        return container[index];
    }
};

template<typename ...Bases>
class Combine : public Bases... {};

int main() {
    Combine<ConstGetter, MutableGetter> container;
    container.get(1); // Member 'get' found in multiple base classes of different types
}

在正常情况下,我只会使用using Super::method;,但这里我不知道派生方法的名称。在一个完美的世界里,我可以使用这样的东西:

template<typename ...Bases>
class Combine : public Bases... {
    using Bases::* ...;
};

但 C++ 不允许这样做。

是否有可能以某种方式实现我的Combine 类?我很确定编译器可以获取所有信息来解决这种极端情况,但我不知道如何提供它以使其工作。

【问题讨论】:

    标签: c++ templates inheritance overloading template-meta-programming


    【解决方案1】:

    奇怪的问题,因为 get() 只在一个类中定义可以完美地工作,但在两个?错误?我很好奇是否存在核心语言缺陷,因为编译器似乎可以解决这个问题,但我没有看到任何问题 (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html)。

    理想的解决方案可能是MutableGetterConstGetter 继承,因为从逻辑上讲,允许突变的吸气剂也应该允许获取非可变版本。

    struct MutableGetter : public ConstGetter {
      using ConstGetter::get;
    
      int &get(int index) {
        return container[index];
      }
    };
    

    这是我能得到的最接近的:

    #include <iostream>
    #include <vector>
    
    struct ContainerProvider {
      std::vector<int> container{1, 2, 3};
    };
    
    struct ConstGetter : public virtual ContainerProvider {
      [[nodiscard]] const int &get(int index) const {
        std::cout << "CONST" << std::endl;
        return container[index];
      }
    };
    
    struct MutableGetter : public virtual ContainerProvider {
      int &get(int index) {
        std::cout << "NON-CONST" << std::endl;
        return container[index];
      }
    };
    
    template <typename... Bases>
    class Combine;
    
    template <typename B1, typename B2, typename... Bases>
    struct Combine<B1, B2, Bases...> : public B1, public Combine<B2, Bases...> {
      using B1::get;
      using Combine<B2, Bases...>::get;
    };
    
    template<typename B>
    struct Combine<B> : B {
      using B::get;
    };
    
    int main() {
      Combine<ConstGetter, MutableGetter> container;
      std::cout << container.get(0) << std::endl;  // non-const
      const auto &const_container = container;
      std::cout << const_container.get(0) << std::endl;  // const
    }
    

    不过,Combine 必须知道其父级公开了哪些成员函数,这真的很糟糕。如果您愿意放弃成员函数的想法并偶尔进行强制转换,我找到了一个部分解决方案......:使用朋友函数

    #include <iostream>
    #include <vector>
    
    struct ContainerProvider {
      std::vector<int> container{1, 2, 3};
    };
    
    class ConstGetter : public virtual ContainerProvider {
      [[nodiscard]] const int &get(int index) const {
        std::cout << "CONST" << std::endl;
        return container[index];
      }
    
      friend [[nodiscard]] const int& get(const ConstGetter &self, int i) { return self.get(i);  }
    };
    
    class MutableGetter : public virtual ContainerProvider {
      int &get(int index) {
        std::cout << "NON-CONST" << std::endl;
        return container[index];
      }
    
      friend [[nodiscard]] const int &get(MutableGetter &self, int i) {
        return self.get(i);
      }
    };
    
    template <typename... Bases>
    class Combine : public Bases... {};
    
    int main() {
      Combine<ConstGetter, MutableGetter> container;
      std::cout << get((MutableGetter&)container, 0) << std::endl;
      const auto &const_container = container;
      std::cout << get(const_container, 0) << std::endl;
    }
    

    【讨论】:

    • 如果一个类从两个父类继承了两个签名几乎相同的方法,你必须给编译器一个线索来消除调用的歧义
    • @MatG 这很清楚,但是你知道为什么这个实例不能消除歧义,而类似的例子(例如两个函数在同一个类中)可以吗?如果是这样,也许您可​​以将其发布为对 OP 问题的回答。
    • 至于为什么重载解析不能跨类工作,你可以查看这个旧的post 和这个questionanswer
    【解决方案2】:

    我想将ConstGetter::get() 重命名为ConstGetter::const_get 不是一种选择?但是,这个编译:container.ConstGetter::get(); container.MutableGetter::get();,检查它here


    至于为什么重载解析不能跨类工作,你可以检查 这个老post 还有这个questionanswer

    【讨论】:

    • 是的,这会起作用,但我想让Combine&lt;...&gt; 尽可能通用,因此它适用于任何给定的类。而在方法调用中指定父级只会使接口更加复杂。我想我现在要么坚持这个选项,要么重新考虑我正在尝试编写的库的整个界面。
    • @eL'teammate 你肯定有更大的目标,我无法从你的小 sn-p 中获得:使用模板化多重继承来区分 const/non const getter 对我来说似乎很扭曲 :-) 我的关于界面设计的小建议是:让用户在调用您的设施时能够清楚地表达他们的意图
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-23
    • 2011-02-09
    • 1970-01-01
    • 2020-08-12
    • 1970-01-01
    相关资源
    最近更新 更多