2015-02-05 47 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