在多重继承中消除类成员的歧义 [英] Disambiguate class-member in multiple inheritance

查看:93
本文介绍了在多重继承中消除类成员的歧义的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有这个可变的基类模板:

Suppose I have this variadic base class-template:

template <typename ... Types>
class Base
{
public:
    // The member foo() can only be called when its template 
    // parameter is contained within the Types ... pack.

    template <typename T>
    typename std::enable_if<Contains<T, Types ...>::value>::type
    foo() {
        std::cout << "Base::foo()\n";
    }
};

仅当其模板参数与Base的参数中的至少一个匹配时,才能调用foo()成员(Contains的实现在本文的底部列出):

The foo() member can only be called when its template-parameter matches at least one of the parameters of Base (the implementation of Contains is listed at the bottom at this post):

Base<int, char>().foo<int>(); // fine
Base<int, char>().foo<void>(); // error

现在,我使用类型为 非重叠集 的方法定义从Base继承两次的派生类:

Now I define a derived class that inherits twice from Base, using non-overlapping sets of types:

struct Derived: public Base<int, char>,
                public Base<double, void>
{};

我希望在致电时例如

Derived().foo<int>();

编译器会找出要使用的基类,因为它是SFINAE,而不包含int.但是,GCC 4.9和Clang 3.5都抱怨调用不明确.

the compiler would figure out which base-class to use, because it is SFINAE'd out of the one that does not contain int. However, both GCC 4.9 and Clang 3.5 complain about an ambiguous call.

那么我的问题有两个:

  1. 为什么编译器不能解决这种歧义(普遍利益)?
  2. 我可以做些什么而不用编写Derived().Base<int, char>::foo<int>();来完成这项工作? 编辑:GuyGreer向我显示,当我添加两个using-声明时,该呼叫已消除歧义.但是,由于我为用户提供了继承的基类,因此这不是理想的解决方案.如果可能的话,我不希望我的用户必须将这些声明(对于大型类型列表可能是非常冗长和重复的)添加到其派生类中.
  1. Why can't the compiler resolve this ambiguity (general interest)?
  2. What can I do to make this work, without having to write Derived().Base<int, char>::foo<int>();? GuyGreer showed me that the call is disambiguated when I add two using-declarations. However, since I'm providing the base-class for the user to inherit from, this isn't an ideal solution. If at all possible, I don't want my users to have to add those declarations (which can be quite verbose and repetitive for large type-lists) to their derived classes.

Contains的实现:

template <typename T, typename ... Pack>
struct Contains;

template <typename T>
struct Contains<T>: public std::false_type
{};

template <typename T, typename ... Pack>
struct Contains<T, T, Pack ...>: public std::true_type
{};

template <typename T, typename U, typename ... Pack>
struct Contains<T, U, Pack ...>: public Contains<T, Pack...>
{};

推荐答案

下面是一个简单的示例:

Here's a simpler example:

template <typename T>
class Base2 {
public:
    void foo(T ) { }
};

struct Derived: public Base2<int>,
                public Base2<double>
{};

int main()
{
    Derived().foo(0); // error
}

其原因来自合并规则[class.member.lookup]:

The reason for that comes from the merge rules [class.member.lookup]:

否则(即C不包含f的声明或结果声明集为空),S(f,C)为 最初是空的.如果C具有基类,则在每个直接基类子对象Bi中计算f的查找集, 并将每个这样的查询集S(f,Bi)依次合并为S(f,C).
— [..]
—否则,如果S(f,Bi)和S(f,C)的声明集不同,则合并是不明确的...

Otherwise (i.e., C does not contain a declaration of f or the resulting declaration set is empty), S(f,C) is initially empty. If C has base classes, calculate the lookup set for f in each direct base class subobject Bi, and merge each such lookup set S(f,Bi) in turn into S(f,C).
— [..]
— Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous...

由于初始声明集为空(Derived中没有任何方法),因此我们必须从所有基础中进行合并-但是我们的基础具有不同的集合,因此合并失败.但是,该规则仅在C(Derived)的声明集为空的情况下才明确适用.因此,为了避免这种情况,我们将其设为非空:

Since our initial declaration set is empty (Derived has no methods in it), we have to merge from all of our bases - but our bases have differing sets, so the merge fails. However, that rule explicitly only applies if the declaration set of C (Derived) is empty. So to avoid it, we make it non-empty:

struct Derived: public Base2<int>,
                public Base2<double>
{
    using Base2<int>::foo;
    using Base2<double>::foo;
};

之所以可行,是因为应用using的规则是

That works because the rule for applying using is

在声明集中, using-declarations 替换为声明集 未被派生类(7.3.3)的成员隐藏或覆盖的指定成员的数量,

In the declaration set, using-declarations are replaced by the set of designated members that are not hidden or overridden by members of the derived class (7.3.3),

关于成员是否不同,这里没有任何评论-我们实际上只是为Derived提供foo的两个重载,绕过成员名称查找合并规则.

There's no comment there about whether or not the members differ - we effectively just provide Derived with two overloads on foo, bypassing the member name lookup merge rules.

现在,Derived().foo(0)明确地调用Base2<int>::foo(int ).

除了显式地为每个碱基都具有using之外,您还可以编写一个收集器来完成所有任务:

Alternatively to having a using for each base explicitly, you could write a collector to do them all:

template <typename... Bases>
struct BaseCollector;

template <typename Base>
struct BaseCollector<Base> : Base
{
    using Base::foo;
};

template <typename Base, typename... Bases>
struct BaseCollector<Base, Bases...> : Base, BaseCollector<Bases...>
{
    using Base::foo;
    using BaseCollector<Bases...>::foo;
};

struct Derived : BaseCollector<Base2<int>, Base2<std::string>>
{ };

int main() {
    Derived().foo(0); // OK
    Derived().foo(std::string("Hello")); // OK
}

在C ++ 17中,您可以 pack扩展using声明,这意味着可以将其简化为:

In C++17, you can pack expand using declarations also, which means that this can be simplified into:

template <typename... Bases>
struct BaseCollector : Bases...
{
    using Bases::foo...;
};

这不仅编写时间短,而且编译效率更高.双赢.

This isn't just shorter to write, it's also more efficient to compile. Win-win.

这篇关于在多重继承中消除类成员的歧义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆