2012-03-29 34 views
0

我有以下代碼:C++:重載不選擇預期的方法

#include <iostream> 
#include <vector> 
using namespace std; 

struct A{}; 
struct B: public A {}; 

template <typename T> 
void foo(const T& obj) { cerr << "Generic case"<< endl;} 

void foo(const A& a) { 
    cerr << "Specific case" << endl; 
} 

int main() { 
    vector<int> v; 
    foo(v); 
    B b; 
    foo(b); 
    A a; 
    foo(a); 
} 

輸出是

  • 通用情況
  • 通用情況
  • 具體情況

爲什麼foo(const A& a)沒有被選中對於B對象?

奇怪的是,如果我刪除了模板方法,只是有以下幾點:

#include <iostream> 
#include <vector> 

struct A{}; 
struct B: public A {}; 

//template <typename T> 
//void foo(const T& obj) { cerr << "Generic case"<< endl;} 

void foo(const A& a) { 
    cerr << "Specific case" << endl; 
} 

int main() { 
    B b; 
    foo(b); 
    A a; 
    foo(a); 
} 

代碼編譯,輸出爲:

Specific case 
Specific case 

爲什麼模板方法制作的存在這樣的區別?

編輯:如何強制編譯器在模板方法的存在 中選擇從A派生類的自由方法?

回答

11

foo(const B&)的調用沒有必要轉換,模板實例化產生的結果是更好的匹配。

當編譯器看到一個函數調用時,每個基函數模板都必須實例化,並與每個正常函數一起包含在過載集合中。之後執行重載解析。還有SFINAE,它允許實例化一個函數模板導致錯誤(這樣的函數不會被添加到超載集合中)。當然,事情並不那麼簡單,但它應該給出總體情況。

關於您的編輯:只有一種方法可以調用。還有什麼可以作爲輸出?

+0

是的 - 只有一種方法可以簡化事情。我想我也期待const B&call在這種情況下也會失敗。 – ATemp 2012-03-29 18:57:51

+0

'B'是'A'的直接孩子。它可以綁定到'A&'或'const A&'。 – pmr 2012-03-29 19:01:37

+0

現在我意識到我已經花了31分鐘撰寫我的答案......所以吃了我的時間:x – 2012-03-29 19:22:35

1

@ pmr's answer解釋了爲什麼在您的示例中首選模板函數。要強制編譯器選擇您的超載,可以使用SFINAE從超載集中刪除模板化函數。更改模板foo

template <typename T> 
typename std::enable_if<!std::is_base_of<A, T>::value>::type 
    foo(const T& obj) { cerr << "Generic case"<< endl;} 

現在,如果TA或從A派生的類的模板函數的返回類型是無效的,它將從重載決議中排除。 enable_if存在於type_traits標題中。

+0

我不得不從foo()返回一些東西嗎?如果我想保持方法簽名相同,該怎麼辦?是否可以更改foo()中的代碼並使用enable_if來防止由於foo()的方法體造成的實例化? – ATemp 2012-03-29 20:41:11

+0

@ATemp'enable_if'有2個模板參數,其中第二個是返回類型。它默認爲「無效」,這就是爲什麼我在示例中省略它的原因。如果您有不同的返回類型,只需添加第二個模板參數即可。由於其正文中的代碼,您無法防止在重載解析期間選擇模板化函數。 – Praetorian 2012-03-29 22:07:22

4

是的,它有點令人驚訝,但是當重載解決方案出現時,繼承和模板不能很好地混合。

問題是,在評估應選擇哪個重載時,編譯器會選擇一個必需的轉換次數最少的轉換器(內置於對非顯式構造函數或轉換運算符的內置派生到基本的調用等等......)。排名算法實際上非常複雜(並不是所有的轉化都是相同的......)。

一旦超載被排名,如果兩個最頂級排名相同,一個是模板,那麼該模板將被丟棄。但是,如果模板的排名高於非模板(通常轉化次數較少),則會選擇該模板。

你的情況:

  • std::vector<int>只有一個超負荷的比賽,因此它被選中。
  • 對於A兩個過載匹配,它們排名相同,模板被丟棄。
  • 對於B兩個過載匹配,模板排名較高(不需要從派生到基礎的轉換),它被選中。

有兩種解決方法,最簡單的就是「修理」的號召網站:

A const& ba = b; 
foo(ba); 

另一種是固定模板本身,然而,這是麻煩......

您可以硬編碼從A派生類這是不是你希望過載:

template <typename T> 
typename std::enable_if<not std::is_base_of<A, T>::value>::type 
foo(T const& t) { 
    std::cerr << "Generic case\n"; 
} 

然而事實並非如此flexib le ...

另一個解決方案是定義一個鉤子。首先,我們需要一些元編程工具:

// Utility 
template <typename T, typename Result = void> 
struct enable: std::enable_if< std::is_same<T, std::true_type>::value > {}; 

template <typename T, typename Result = void> 
struct disable: std::enable_if< not std::is_same<T, std::true_type>::value > {}; 

然後我們定義鉤功能:

std::false_type has_specific_foo(...); 

template <typename T> 
auto foo(T const& t) -> typename disable<decltype(has_specific_foo(t))>::type { 
    std::cerr << "Generic case\n"; 
} 

然後對每個基類,我們希望有一個具體的富:

std::true_type has_specific_foo(A const&); 

在採取行動ideone

它也可能在C++ 03中,但稍微麻煩一些。雖然這個想法是一樣的,但省略號的參數...的排名最差,所以我們可以在另一個函數上使用重載選擇來推動主選擇的選擇。