2011-01-25 169 views
3

如果我們在這一函數模板,模板參數推導

template<typename T> 
void f(T param) {} 

然後我們就可以把它在以下幾個方面,

int i=0; 
f<int>(i);//T=int : no need to deduce T 
f(i); //T=int : deduced T from the function argument! 

//likewise 
sample s; 
f(s); //T=sample : deduced T from the function argument! 

現在考慮上述函數模板的這個變體,

template<typename TArg, typename TBody> 
void g(TArg param) 
{ 
    TBody v=param.member; 
} 

現在,可以在編譯器推斷模板參數,如果我們寫,

sample s; 
g(s); //TArg=sample, TBody=int?? 

假設sample被定義爲,

struct sample 
{ 
    int member; 
}; 

基本上有兩個問題:

  • 編譯器可以推斷出在第二個例子中的模板參數?
  • 如果不是,那爲什麼?有什麼困難嗎?如果標準沒有說出「從函數體」「模板參數推導的任何內容,那麼是不是因爲參數不能推導出來?或者它沒有考慮這樣的推論,以避免增加語言的複雜性?或者是什麼?

我想知道你對這樣的推論的看法。


編輯:

順便說,海灣合作委員會是能夠推斷出函數的參數,如果我們寫這樣的代碼:http://www.ideone.com/cvXEA

不:

template<typename T> 
void h(T p) 
{ 
     cout << "g() " << p << endl; 
     return; 
} 
template<typename T> 
void g(T p) 
{ 
     h(p.member); //if here GCC can deduce T for h(), then why not TBody in the previous example? 
     return; 
} 

在這個例子中工作示範上例的工作演示:http://www.ideone.com/UX038

回答

0

沒有編譯器可能以一致的方式實現此功能。你只是要求太多。

+0

請你詳細說明一下嗎? – Nawaz 2011-01-25 21:50:32

0

TBody可能不明確,因爲sample可能不是唯一具有member成員的類型。此外,如果g調用其他模板函數,編譯器無法知道可能對TBody施加了什麼其他限制。

因此,在某些邊緣情況下,理論上可以推導出TBody的正確類型,通常情況下不可以。

5

您可能已經得出結論,編譯器將不會通過檢查sample.member的類型推斷出TBody。這會給模板演繹算法增加另一個複雜程度。

模板匹配算法只考慮功能簽名,而不是他們的身體。雖然沒有使用過於頻繁,這是完全合法的,只是聲明瞭一個模板功能,而不提供身體:

template <typename T> void f(T param); 

這滿足編譯器。爲了滿足鏈接器,當然你也必須在某處定義函數體,並確保提供了所有必需的實例。但函數體確實不是而是必須對模板函數的客戶端代碼可見,只要所需的實例在鏈接時可用即可。該機構將必須顯式實例化功能,如:

template <> void f(int param); 

但這隻能部分適用於您的問題,因爲你可以想像這樣的以下內容,其中第2個參數可以從所提供的默認推導出一個場景參數,並且將未編譯:

template<typename TArg, typename TBody> 
void g(TArg param, TBody body = param.member); // won't deduce TBody from TArg 

模板匹配算法考慮在類或結構情況下,只有實際類型,而不是任何潛在的嵌套成員類型。這會增加另一層級的複雜性,顯然被認爲太複雜。算法在哪裏停止?成員的成員等等也被考慮在內?

此外,它不是必需的,因爲還有其他方式實現相同的意圖,如下面的示例所示。

沒有什麼能阻止你從寫作:

struct sample 
{ 
    typedef int MemberType; 
    MemberType member; 
}; 

template<typename TArg> 
void g(TArg param) 
{ 
    typename TArg::MemberType v = param.member; 
} 

sample s = { 0 }; 
g(s); 

,以獲得相同的效果。


關於你編輯後加入您的樣品:而似乎h(p.member)不依賴於結構的部件上,因此模板匹配算法失效,它不會因爲你來了兩步過程:

  1. 見狀g(s);,編譯器會採取sample類型的參數中的任何功能(模板或不!)。在你的情況下,最好的匹配是void g(T p)至此,編譯器甚至沒有看過g(T p)的身體呢!
  2. 現在,編譯器創建一個g(T p)的實例,專用於T: sample。因此,當它看到h(p.member)它知道p.member類型爲int,並且將嘗試找到一個函數h()採用int類型的參數。你的模板功能h(T p)原來是最好的匹配。

需要注意的是,如果你寫了(注意NOT_A_member):

template<typename T> 
void g(T p) 
{ 
     h(p.NOT_A_member); 
     return; 
} 

那麼編譯器仍然會考慮在g()階段1有效的匹配,那麼你得到一個錯誤事實證明,當那sample沒有一個叫NOT_A_member的成員。

+0

或`BOOST_AUTO(v,param.member)` – Anycorn 2011-01-25 21:39:28

0

編譯器不可能對您提供的代碼執行一些操作,其中第一項是推導出第二個模板參數TBody。首先,類型推導僅適用於編譯器試圖匹配調用時函數的參數。此時甚至沒有看到模板化函數的定義。

對於額外的功勞,即使編譯器要查看函數定義,代碼TBody v = parameter.member本身也是不可推導的,因爲有潛在的無限數據類型可以在構造函數中接受parameter.member

現在,在第二塊代碼上。爲了理解它,模板編譯的整個過程在編譯器在該位置看到函數調用g(x)時開始。編譯器認爲最好的候選者是模板函數template <typename T> void g(T),並確定類型T是什麼作爲重載解析的一部分。一旦編譯器確定它是對模板的調用,它將對該函數執行第一次編譯。

在第一遍期間,句法檢查是不帶類型的實際替代執行的,所以模板參數T仍然任何型和參數p是一個尚未未知類型的。在第一次通過期間,代碼被驗證,但是依賴於名稱被跳過,它們的含義只是假設。當編譯器看到p.member,pT這是一個模板參數時,它假定它將是尚未知類型的成員(這就是爲什麼如果它是一種類型,您將不得不在此處使用typename )。調用h(p.member);也依賴於類型參數T並保持原樣,假設一旦發生類型替換,所有事情都會有意義。

然後編譯器確實替代了類型。在此步驟T不再是類型類型,但它代表具體類型sample。現在編譯器在第二遍時嘗試填寫第一遍時留下的空白。當它看到p.member它看起來類型內member,並確定它是一個int,並嘗試用該知識解決呼叫h(p.member);。因爲T類型在第二階段之前已經解決,所以這相當於外部調用g(x):所有類型都是已知的,並且編譯器只需要解析函數調用h的最佳過載,該函數採用int&類型的參數,並且整個過程再次開始,模板h被發現是最佳候選者,並且...

元編程明白類型演繹僅在函數的實際簽名而不是主體上執行是非常重要的,這對於初學者來說並不重要。在函數簽名中使用enable_if(來自boost或其他地方)作爲參數或返回類型不是巧合,但是使編譯器無法替換之前的類型的唯一方法是將模板選擇爲最佳候選並且替換失敗爲變成了一個實際的錯誤(而不是SFINAE)