2015-02-05 44 views
4

假设我有这个可变参数的基类模板:C++ 11:消除歧义类成员在多重继承

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"; 
    } 
}; 

其模板的参数相匹配的至少一个时,foo()构件只能被称为的Base参数(Contains执行在底部列出在此篇):

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

现在我定义了一个派生类从基地继承两次,使用非重叠组类型:

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

我希望例如当调用该

Derived().foo<int>(); 

,编译器会找出使用哪个基类,因为它是SFINAE'd出不包含int的一个。然而,GCC 4.9和Clang 3.5都抱怨模糊的电话。然后

我的问题是双重的:

  1. 为什么不能编译器解决这种不确定性(一般利益)?
  2. 我能做些什么来完成这项工作,而无需编写Derived().Base<int, char>::foo<int>();编辑: GuyGreer告诉我,当我添加两个使用声明时,调用是消歧的。但是,由于我为用户提供了继承的基类,因此这不是一个理想的解决方案。如果可能的话,我不希望我的用户不得不将这些声明(对于大型类型列表可能非常冗长和重复)添加到它们的派生类中。

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...> 
{}; 
+1

这是一般规律的体现,从不同范围的名称不使用C超载++。 – 2015-02-05 16:25:23

回答

9

这里有一个简单的例子:

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]:

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

因为我们最初的声明集为空(Derived中有没有方法),我们必须从我们所有的基地的合并 - 但我们的基地有不同组,因此合并失败。但是,该规则明确仅适用于CDerived)为空的声明集。因此,为了避免它,我们将其非空:

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

做是因为用于应用using规则是

在宣言中,使用-声明由集 被替换的未被隐藏或被派生类的成员覆盖的指定成员(7.3.3),

关于是否成员di ffer - 我们有效地仅在foo上提供Derived两个重载,绕过成员名称查找合并规则。

现在,Derived().foo(0)明确要求Base2<int>::foo(int)

或者具有用于各基地using明确,你可以写一个收集器做所有这些:

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 
} 
+0

谢谢,我明白现在模糊的地方。但是,正如我在GuyGreer的回答和编辑问题中所提到的,该解决方案对我的用户来说并不是很友善。我想,如果没有简单的解决方法,我只需要仔细记录。 – JorenHeit 2015-02-05 16:30:15

+0

@JorenHeit如果您愿意在层次结构中引入更多类,那么会收集一组“Base”实例,并让用户从该实例继承,您可以使其工作。 – jrok 2015-02-05 16:33:25

+0

@jrok但是我不得不知道'Base'将被实例化的类型,对吗? – JorenHeit 2015-02-05 16:34:19

3

虽然我不能详细告诉你为什么它不作为是工作,我加入using Base<int, char>::foo;using Base<double, void>::foo;Derived,现在编译罚款。

测试与clang-3.4gcc-4.9

+0

这很有趣... +1。但是,如果可能的话,我不希望我的用户在从我提供的基类继承时不得不将这些使用声明添加到它们的代码中。不过谢谢! – JorenHeit 2015-02-05 16:10:15

+1

@JorenHeit我不知道为什么从'Base'派生两次是有意义的。确定它们不重叠,但为什么不直接从'Base '派生? – SirGuy 2015-02-05 16:12:59

+1

这是你需要'using'的原因:http://stackoverflow.com/a/5368930/502399 – 2015-02-05 16:14:19