2012-11-21 45 views
5

這是一種關於 this的問題。超載分辨率解析爲一個不可見的功能

#include <iostream> 

struct type1 {}; 
struct type2 {}; 

void foo(type1 x) 
{ 
    std::cout << "foo(type1)" << std::endl; 
} 

template<typename T> 
void bar() { 
    foo(T()); 
} 

int main() 
{ 
    bar<type1>(); 
    bar<type2>(); 
    return 0; 
} 

void foo(type2 x) 
{ 
    std::cout << "foo(type2)" << std::endl; 
} 

在上面的代碼foo(type2)不在的bar<type2>實例化在main時可見。然而代碼編譯,併產生以下的輸出:

foo(type1) 
foo(type2) 

編譯器如何知道foo(type2)實例中mainbar<type2>時可用?

編輯:我想了解更多關於模板實例化過程中重載解析如何工作。考慮下面的代碼:

#include <iostream> 

struct type1 {}; 
struct type2 {}; 
struct type3 { 
    operator type2() { return type2(); } 
}; 

void foo(type1 x) 
{ 
    std::cout << "foo(type1)" << std::endl; 
} 

void foo(type2 x) 
{ 
    std::cout << "foo(type2)" << std::endl; 
} 

int main() 
{ 
    foo(type3()); 
    return 0; 
} 

void foo(type3 x) 
{ 
    std::cout << "foo(type3)" << std::endl; 
} 

輸出是

foo(type2) 

即使更匹配foo(type3)可用,呼叫foo(type3())解析爲foo(type2),因爲這是已經被解析由編譯器的唯一候選人直到那一刻。現在考慮下面的代碼:

#include <iostream> 

struct type1 {}; 
struct type2 {}; 
struct type3 { 
    operator type2() { return type2(); } 
}; 

void foo(type2 x) 
{ 
    std::cout << "foo(type2)" << std::endl; 
} 

template<typename T> 
void bar() { 
    foo(T()); 
} 

int main() 
{ 
    bar<type3>(); 
    return 0; 
} 

void foo(type3 x) 
{ 
    std::cout << "foo(type3)" << std::endl; 
} 

輸出是

foo(type3) 

也就是說,在呼叫bar<type3>()的地步,即使只foo(type2)是可見的,編譯器仍然挑選foo(type3),後來因爲來這是一個更接近的匹配。

回答

3

答案可以通過參數相關名稱查找(ADL)(鏈接問題中也提到)找到。 foo(T());有兩個查找。首先在模板定義時間,定義點處定義的所有函數都包含在過載集合中。這意味着當編譯器在bar內部看到foo(T());時,它將僅添加到void foo(type1 x)到過載集。然而,有一個第二個查找執行,稱爲ADL。在模板實例化時間,即bar<type2>();它在與提供的參數相同的名稱空間中尋找foo,在這種情況下其爲type2。由於type2位於全局名稱空間中,因此它會查找foo,它在全局名稱空間中執行type2並找到它並解析調用。如果您正在從標準中查找信息,請參閱14.6.4.2 Candidate functions

請嘗試以下操作並觀察代碼失敗。這是因爲它在與a::type1相同的名稱空間中找不到foo

#include <iostream> 

namespace a 
{ 
    struct type1 {}; 
} 

template<typename T> 
void bar() { 
    foo(T()); 
} 

int main() 
{ 
    bar<a::type1>(); 
    return 0; 
} 

void foo(a::type1 x) 
{ 
    std::cout << "foo(a::type1)" << std::endl; 
} 
+0

你的代碼用GCC 4.6編譯得很好。 – keveman

+0

@keveman:引自[here](http://gcc.gnu.org/gcc-4.7/changes.html):'G ++現在正確地實現了兩階段查找規則,這樣在模板中使用的非限定名稱必須有一個適當的聲明可以在模板的定義點範圍內找到,或者通過在實例化處的參數相關查找來找到。 –

+0

好的。它使用gcc 4.7失敗。謝謝。儘管我的結論是,在我的例子中,'foo(type3)'既不在模板定義的範圍內,也不在實例化點。這仍然是被調用的那個。 – keveman

6

沒有定義的任何符號將在鏈接過程中被替換,因爲函數foo(type2)可能已在另一個文件中提供。

編譯器要說的是,當沒有進一步的替換可以應用時,所需的函數是否已經在整個過程結束時定義。

爲了明確的理解,你必須知道的編譯,比方說,一個常見的C程序所需的步驟:

  • 第一,你擴大你的代碼的所有宏;

  • 然後根據語言語法對您的代碼進行驗證,以便可以將其轉換爲彙編語言 - 編譯過程本身;在此步驟中,找到的沒有定義的每個符號都將在表格中註明,條目號爲(symbol, definition),應在稍後完成,以便您的程序能夠正確構建;接下來,編譯到程序集中的代碼將被轉換爲機器語言,即將創建對象;然後,

  • 最後,你需要鏈接你已經可執行的對象,以解決對符號定義的任何依賴;這最後一步檢查您的對象是否有未定義的符號,從其他模塊或庫中添加定義,從而完成該程序。

如果任何符號不正確「鏈接」到它的定義,編譯器將指出錯誤在你的程序 - 經典undefined reference to...

考慮到您發佈的代碼,該進程將被執行,直到它到達編譯器。編譯器會遍歷代碼,請注意type1,type2,foo(type1 x)bar<T>()的定義。

struct type1 {}; 
struct type2 {}; 

當它會到達主,它會找到呼籲bar<type1>();,並稱之爲foo(type1()),這已經是衆所周知的,可以正常使用了。

void foo(type1 x) { 
    std::cout << "foo(type1)" << std::endl; 
} 

template<typename T> 
void bar() { 
    foo(T()); 
} 

int main() { 

    bar<type1>(); 
    bar<type2>(); 
    return 0; 

} 

一旦它會達到下一個電話,bar<type2>();,它會嘗試調用foo(type2()),但沒有這樣的定義,將可用於使用,所以這將涉及該呼叫是未知的符號,而必須更換通過後面的過程中的定義。

編譯器運行main後,它達到一個新的定義,這正是創建「轉換表」時缺少定義的那個定義。

void foo(type2 x) { 
    std::cout << "foo(type2)" << std::endl; 
} 

因此,在接下來的步驟中,編譯能夠用其各自的定義替換符號,並且程序編譯正確。

問候!

+0

仔細觀察,在'main'之前甚至沒有'foo(type2)'聲明。那麼編譯器在實例化'bar '的時候怎麼知道'foo(type2)'最終會被看到? – keveman

+0

這正是我所指出的:編譯器找到的每個符號都沒有可用的定義,它被列在「轉換表」中,這必須在鏈接編輯過程結束時完成。如果這樣的表有任何沒有已知值的鍵,編譯器就會抱怨,並且經典的'未定義的引用...'會出現;否則,將執行替換。 – Rubens

+0

感謝您的詳細解答,但恐怕您已經陷入無關切線。這裏真正的問題是關於編譯時重載分辨率,而不是關於鏈接時間。在'bar '實例化的第二階段名稱查找中,C++編譯器明顯在做一些ADL來確定'foo(type2)'是正確的'foo',儘管它還沒有看到' FOO(TYPE2)'。我可能錯過了關於實例化發生的方式和時間。 – keveman