2012-03-07 83 views
7

模板聲明與模板定義的匹配程度如何?如果「模板名稱引用了相同的模板和[...]」(14.4 [temp.type]),我在標準模板標識中發現了一些文字, p1)但我找不到模板名稱模板名稱指向相同模板的定義。我不確定我是否在正確的軌道上,因爲我還沒有足夠好地分解語法以確定模板的定義/聲明是否包含9模板ID ,或者只是使用模板。模板定義如何與模板聲明相匹配?

例如,以下程序正常工作。

#include <iostream> 

template<typename T> 
T foo(T t); 

int main() { 
    foo(1); 
} 

template<typename T> 
T foo(T t) 
{ std::cout << "A\n"; return 0; } 

如果我改變我的模板定義的名稱顯然不再指同一模板使用模板參數的方式,連接失敗。

#include <iostream> 

template<typename T> 
T foo(T t); 

int main() { 
    foo(1); 
} 

template<typename T> 
int foo(T t) { std::cout << "A\n"; return 0; } 

// or 

template<typename T> 
struct identity { 
    typedef T type; 
}; 

template<typename T> 
typename identity<T>::type 
foo(T t) { std::cout << "A\n"; return 0; } 

接下來,如果我的模板定義移動到另一個翻譯單位,爲我實現C++(MSVC 11測試版)的程序,無論我怎麼說的各類作品。

//main.cpp 

template<typename T> 
T foo(T t); 

int main() { 
    foo(1); 
} 

//definition.cpp 
#include <iostream> 

template<typename T> 
struct identity { 
    typedef T type; 
}; 

template<typename T> 
typename identity<T>::type 
foo(T t) { std::cout << "A\n"; return 0; } 

template int foo<int>(int); 

//definition.cpp 
#include <iostream> 

template<typename T> 
int foo(T t) { std::cout << "A\n"; return 0; } 

template int foo<int>(int); 

或者即使定義不是一個模板都:

//definition.cpp 
#include <iostream> 

int foo(T t) { std::cout << "A\n"; return 0; } 

顯然鏈接是成功的,因爲簽名/重整名稱是一樣的,不管被實例化以創建符號的模板。我覺得這個不確定的行爲,因爲我違反了:一類模板的

§14.1 [溫度] P6

函數模板,類模板的成員函數或靜態 數據成員應除非 對應的專業化被明確實例化(14.7.2)在 某些翻譯單元中;否則在每個翻譯 定義它隱式實例化(14.7.1)的單位;不需要診斷。

但隨後說我嘗試把模板的定義在第二個翻譯單元,幷包括一個顯式實例在兩個位置之一,以滿足這些要求:

#include <iostream> 

template<typename T> 
T foo(T t) { std::cout << "A\n"; return 0; } 

// Location 1  

template<typename T> 
int foo(int t) { std::cout << "B\n"; return 0; } 

// Location 2 

哪些規則關於明確實例化引用的模板的歧義?將它放在位置1會導致實例化正確的模板,並將該定義用於最終的程序,同時將它放在位置2處實例化另一個模板,並導致我認爲在上面14.1 p6下未定義的行爲。

另外的兩個模板定義的隱式實例挑選第一個模板不管是什麼,因此它似乎爲消除歧義模板的規則是在這種情況下不同:

#include <iostream> 

template<typename T> 
T foo(T t) { std::cout << "A\n"; return 0; } 

template<typename T> 
int foo(int t) { std::cout << "B\n"; return 0; } 

int main() { 
    foo(1); // prints "A" 
} 

此次前來的原因提問者發現,一個向前聲明凡達有關this question

template<typename T> 
T CastScriptVarConst(const ScriptVar_t& s); 

不能充當多個模板定義聲明:

template<typename T> 
typename std::enable_if<GetType<T>::value < SVT_BASEOBJ,T>::type 
CastScriptVarConst(const ScriptVar_t& s) { 
    return (T) s; 
} 

template<typename T> 
typename std::enable_if<!(GetType<T>::value < SVT_BASEOBJ) 
         && std::is_base_of<CustomVar,T>::value,T>::type 
CastScriptVarConst(const ScriptVar_t& s) { 
    return *s.as<T>(); 
} 

我想更好地理解模板定義和聲明之間的關係。

+0

拿起一份「C++模板:完整指南」的完整故事。是的,它比標準更容易消化...實際上很多;) – 0xC0000022L 2012-03-07 19:21:59

回答

4

好吧,讓我們從頭開始。模板的「模板名稱」是模板化的功能或類的實際名稱;即

template<class T> T foo(T t); 

foo是模板名稱。對於函數模板,決定它們是否相同的規則很長,詳見14.5.5.1「函數模板重載」。該節的第6段(我在此引用C++ 03,因此C++ 11中的措辭和段落號可能已更改)定義了當應用於涉及模板的表達式時,相當於和功能上等效的參數。

總之,相當於表情都是一樣的除了有可能爲模板參數不同的名稱,並功能上等同表達式是相同的,如果他們碰巧評價同一件事。例如,前兩個f聲明是相當於,但第三隻功能上等同其他兩個: -

template<int A, int B> 
void f(array<A + B>); 
template<int T1, int T2> 
void f(array<T1 + T2>); 
template<int A, int B> 
void f(array< mpl::plus< mpl::int<A>, mpl::int<B> >::value >); 

它接着在第7段延伸這兩個定義,以全功能模板。匹配的兩個函數模板(在名稱,範圍和模板參數列表中)是等價的,如果它們也具有等效的返回類型和參數類型,或者在功能上等同於函數等效的返回類型和參數類型。看你的第二個例子,這兩個功能都只是功能上等同: -

template<typename T> 
T foo(T t); 

template<typename T> 
typename identity<T>::type foo(T t); 

第7的是,「如果一個程序包含在功能上等同,但不等價的函數模板聲明的可怕的警告,該程序關閉是不合格的,不需要診斷。「因此,你的第二個例子是無效的C++。檢測這樣的錯誤需要在函數模板的每個聲明和定義中用AST描述模板表達式中的每個參數和返回類型,這就是爲什麼標準不需要實現來檢測它的原因。 MSVC有理由編譯你的第三個例子,你打算如何,但這樣做有理由打破。

轉到顯式實例化,重要的部分是14.7,「模板實例化和專業化」。第5段不允許以下所有內容:

  • 顯式實例化模板多次;
  • 顯式實例化並明確地專門化相同的模板;
  • 不止一次地爲同一組參數明確地專用一個模板。

此外,「不需要診斷」,因爲它很難檢測到。

因此,要擴大你的顯式實例例如,下面的代碼違反了第二個規則,並且是非法的: -

/* Template definition. */ 
template<typename T> 
T foo(T t) 
{ ... } 

/* Specialization, OK in itself. */ 
template< > 
int foo(int t) 
{ ... } 

/* Explicit instantiation, OK in itself. */ 
template< > 
int foo(int t); 

這無論是明確的專業化和顯式實例的位置是非法的,但當然因爲不需要診斷,您可能會在某些編譯器上獲得有用的結果。還要注意顯式實例化和顯式專業化之間的區別。是病態的下面的例子,因爲它聲明明確分工而不定義它: -

template<typename T> 
T f(T f) 
{ ... } 

template< > 
int f(int); 

void g(void) 
{ f(3); } 

但這個例子是良好的,因爲它有一個顯式實例: -

template<typename T> 
T f(T f) 
{ ... } 

template f(int); 

void g(void) 
{ f(3); } 

< >使所有的差異。同樣要警告的是,即使你定義了一個明確的專門化,在你使用之前,它必須是,否則編譯器可能已經爲該模板生成了隱式實例化。這在14.7.3「明確的專業化」第6段中,在你正在閱讀的地方下面,並且不需要診斷。爲了適應同一個例子,這是病態的: -

template<typename T> 
T f(T f) 
{ ... } 

void g(void) 
{ f(3); } // Implicitly specializes int f(int) 

template< > 
int f(int) // Too late for an explicit specialization 
{ ... } 

如果你不糊塗夠了嗎,來看看你的最後一個例子: -

template<typename T> 
T foo(T t) { ... } 

template<typename T> 
int foo(int t) { ... } 

foo第二個定義是不是第一個定義的專業化。它必須是template< > int foo(int)template<typename T> T foo(T)的專業化。但沒關係:函數重載是允許的,並且允許函數模板和普通函數之間的重載。形式爲foo(3)的調用將始終使用第一個定義,因爲它的模板參數T可以從參數類型中推導出來。第二個定義不允許從參數類型中推導出它的模板參數。只有明確指定T就可以到達第二個定義,並且只有在呼叫也不含糊與第一個定義: -

f<int>(3); // ambiguous 
f<string>(3); // can only be the second one 

做重載函數模板的整個過程太長,這裏描述。閱讀第14.8.3節,如果你有興趣並提出更多的問題:-)