在多重继承中消除类成员的歧义 [英] Disambiguate class-member in multiple inheritance
问题描述
假设我有这个可变的基类模板:
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.
那么我的问题有两个:
- 为什么编译器不能解决这种歧义(普遍利益)?
- 我可以做些什么而不用编写
Derived().Base<int, char>::foo<int>();
来完成这项工作? 编辑:GuyGreer向我显示,当我添加两个using-声明时,该呼叫已消除歧义.但是,由于我为用户提供了继承的基类,因此这不是理想的解决方案.如果可能的话,我不希望我的用户必须将这些声明(对于大型类型列表可能是非常冗长和重复的)添加到其派生类中.
- Why can't the compiler resolve this ambiguity (general interest)?
- 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屋!