名稱空間
變體
操作

函式模板

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 
 
 

函式模板定義了一族函式。

目錄

[編輯] 語法

template < 引數列表 > 函式宣告 (1)
template < 引數列表 > requires 約束 函式宣告 (2) (C++20 起)
帶佔位符的函式宣告 (3) (C++20 起)
export template < 引數列表 > 函式宣告 (4) (C++11 中已移除)

[編輯] 解釋

引數列表 - 一個非空的逗號分隔的模板引數列表,其中每個引數要麼是非型別引數型別引數模板引數,或上述任意一種的引數包(C++11 起)如同任何模板一樣,引數可以被約束(C++20 起)
函式宣告 - 一個函式宣告。宣告的函式名成為模板名。
約束 - 一個約束表示式,用於限制此函式模板接受的模板引數
函式宣告-
帶佔位符
- 一個函式宣告,其中至少一個引數的型別使用佔位符autoConcept auto:模板引數列表將為每個佔位符附加一個發明的引數(參閱下面的縮寫函式模板)

export 是一個可選修飾符,用於將模板宣告為 *匯出*(當與類模板一起使用時,它也宣告其所有成員都已匯出)。例項化匯出模板的檔案不需要包含它們的定義:宣告就足夠了。export 的實現很少見,並且在細節上相互不一致。

(C++11 前)

縮寫函式模板

當佔位符型別(autoConcept auto)出現在函式宣告或函式模板宣告的引數列表中時,該宣告將宣告一個函式模板,並且為每個佔位符附加一個發明模板引數到模板引數列表。

void f1(auto); // same as template<class T> void f1(T)
void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept
void f3(C2 auto...); // same as template<C2... Ts> void f3(Ts...), if C2 is a concept
void f4(const C3 auto*, C4 auto&); // same as template<C3 T, C4 U> void f4(const T*, U&);
 
template<class T, C U>
void g(T x, U y, C auto z); // same as template<class T, C U, C W> void g(T x, U y, W z);

縮寫函式模板可以像所有函式模板一樣被特化。

template<>
void f4<int>(const int*, const double&); // specialization of f4<int, const double>


(C++20 起)

[編輯] 函式模板簽名

每個函式模板都有一個簽名。

模板頭的簽名是模板引數列表,不包括模板引數名和預設實參,以及 requires 子句(如果有)(C++20 起)

函式模板的簽名包含名稱、引數型別列表、返回型別、尾隨 requires 子句(如果有)(C++20 起)以及模板頭的簽名。除了以下情況,其簽名還包含封閉名稱空間。

如果函式模板是一個類成員,其簽名包含函式所屬的類而不是封閉名稱空間。其簽名還包含尾隨 requires 子句(如果有)(C++20 起)、引用限定符(如果有),以及(C++11 起) cv 限定符(如果有)。

如果函式模板是友元且約束涉及封閉模板引數,其簽名包含封閉類而不是封閉名稱空間。

(C++20 起)

[編輯] 函式模板例項化

函式模板本身不是型別,也不是函式。只包含模板定義的原始檔不會生成任何程式碼。為了使任何程式碼出現,必須例項化模板:必須確定模板實參,以便編譯器可以生成實際的函式(或類,來自類模板)。

[編輯] 顯式例項化

template 返回型別 名稱 < 實參列表 > ( 引數列表 ) ; (1)
template 返回型別 名稱 ( 引數列表 ) ; (2)
extern template 返回型別 名稱 < 實參列表 > ( 引數列表 ) ; (3) (C++11 起)
extern template 返回型別 名稱 ( 引數列表 ) ; (4) (C++11 起)
1) 顯式例項化定義(如果所有非預設模板引數都已顯式指定,則不進行模板實參推導
2) 顯式例項化定義,對所有引數進行模板實參推導
3) 顯式例項化宣告(如果所有非預設模板引數都已顯式指定,則不進行模板實參推導)
4) 顯式例項化宣告,對所有引數進行模板實參推導

顯式例項化定義強制例項化它們所引用的函式或成員函式。它可以在模板定義之後的程式中的任何位置出現,並且對於給定的引數列表,在程式中只允許出現一次,不需要診斷。

顯式例項化宣告(`extern template`)阻止隱式例項化:否則會導致隱式例項化的程式碼必須使用程式中其他地方提供的顯式例項化定義。

(C++11 起)

如果函式模板特化或成員函式模板特化的顯式例項化中可以從函式引數推導出尾隨模板實參,則可以不指定它。

template<typename T>
void f(T s)
{
    std::cout << s << '\n';
}
 
template void f<double>(double); // instantiates f<double>(double)
template void f<>(char);         // instantiates f<char>(char), template argument deduced
template void f(int);            // instantiates f<int>(int), template argument deduced

函式模板或類模板成員函式的顯式例項化不能使用 inlineconstexpr。如果顯式例項化的宣告命名了隱式宣告的特殊成員函式,則程式是病態的。

建構函式的顯式例項化不能使用模板引數列表(語法(1)),這也不是必需的,因為它們可以被推匯出來(語法(2))。

預期解構函式的顯式例項化必須命名類的選定解構函式。

(C++20 起)

顯式例項化宣告不會抑制行內函數、`auto` 宣告、引用和類模板特化的隱式例項化。(因此,當作為顯式例項化宣告主題的行內函數被 ODR 使用時,它會為內聯而隱式例項化,但其非內聯副本不會在此翻譯單元中生成)

具有預設實參的函式模板的顯式例項化定義不是實參的使用,也不會嘗試初始化它們。

char* p = 0;
 
template<class T>
T g(T x = &p) { return x; }
 
template int g<int>(int); // OK even though &p isn’t an int.

[編輯] 隱式例項化

當代碼在一個需要函式定義存在的上下文中使用函式,或者如果定義的存在影響程式的語義(C++11 起),並且此特定函式尚未被顯式例項化時,將發生隱式例項化。如果模板實參列表可以從上下文中推導出來,則不需要提供它。

#include <iostream>
 
template<typename T>
void f(T s)
{
    std::cout << s << '\n';
}
 
int main()
{
    f<double>(1); // instantiates and calls f<double>(double)
    f<>('a');     // instantiates and calls f<char>(char)
    f(7);         // instantiates and calls f<int>(int)
    void (*pf)(std::string) = f; // instantiates f<string>(string)
    pf("∇");                     // calls f<string>(string)
}

如果表示式需要常量求值的函式定義,則函式定義的出現被認為會影響程式的語義,即使表示式不需要常量求值,或者常量表達式求值不使用該定義。

template<typename T>
constexpr int f() { return T::value; }
 
template<bool B, typename T>
void g(decltype(B ? f<T>() : 0));
template<bool B, typename T>
void g(...);
 
template<bool B, typename T>
void h(decltype(int{B ? f<T>() : 0}));
template<bool B, typename T>
void h(...);
 
void x()
{
    g<false, int>(0); // OK: B ? f<T>() : 0 is not potentially constant evaluated
    h<false, int>(0); // error: instantiates f<int> even though B evaluates to false
                      // and list-initialization of int from int cannot be narrowing
}
(C++11 起)

注意:完全省略 <> 允許過載決議同時檢查模板和非模板過載。

[編輯] 模板實參推導

為了例項化函式模板,必須知道每個模板實參,但並非每個模板實參都必須指定。如果可能,編譯器將從函式實參中推導缺失的模板實參。這發生在嘗試函式呼叫時和獲取函式模板地址時。

template<typename To, typename From>
To convert(From f);
 
void g(double d) 
{
    int i = convert<int>(d);    // calls convert<int,double>(double)
    char c = convert<char>(d);  // calls convert<char,double>(double)
    int(*ptr)(float) = convert; // instantiates convert<int, float>(float)
}

此機制使得使用模板運算子成為可能,因為除了將其重寫為函式呼叫表示式之外,沒有指定運算子模板引數的語法。

#include <iostream>
 
int main() 
{
    std::cout << "Hello, world" << std::endl;
    // operator<< is looked up via ADL as std::operator<<,
    // then deduced to operator<<<char, std::char_traits<char>> both times
    // std::endl is deduced to &std::endl<char, std::char_traits<char>>
}

模板實參推導在函式模板名稱查詢(可能涉及實參依賴查詢)之後、過載決議之前進行。

詳情請參閱模板實參推導

[編輯] 顯式模板實參

函式模板的模板引數可以從以下來源獲取:

  • 模板實參推導
  • 預設模板實參
  • 顯式指定,可以在以下上下文中進行:
  • 在函式呼叫表示式中
  • 當獲取函式地址時
  • 當初始化函式引用時
  • 當形成成員函式指標時
  • 在顯式特化中
  • 在顯式例項化中
  • 在友元宣告中

沒有辦法為過載運算子轉換函式和建構函式顯式指定模板引數,因為它們是在不使用函式名的情況下呼叫的。

指定的模板實參必須與模板形參型別匹配(即,型別對應型別,非型別對應非型別,模板對應模板)。實參的數量不能多於形參的數量(除非一個形參是形參包,在這種情況下,每個非包形參都必須有一個實參)(C++11 起)

指定的非型別實參必須與相應非型別模板形參的型別匹配,或可轉換為它們

不參與模板實參推導的函式引數(例如,如果相應的模板實參被顯式指定)將轉換為相應函式引數的型別(如通常的過載決議)。

如果存在額外的實參,顯式指定的模板引數包可以透過模板實參推導進行擴充套件。

template<class... Types>
void f(Types... values);
 
void g()
{
    f<int*, float*>(0, 0, 0); // Types = {int*, float*, int}
}
(C++11 起)

[編輯] 模板實參替換

當所有模板實參都已指定、推導或從預設模板實參獲得時,函式形參列表中模板形參的每次使用都會被相應的模板實參替換。

函式模板的替換失敗(即,無法將模板形參替換為推導或提供的模板實參)會從過載集中移除該函式模板。這允許透過模板超程式設計以多種方式操作過載集:詳情請參閱SFINAE

替換後,所有陣列和函式型別的函式引數都調整為指標,所有頂級 cv 限定符都從函式引數中刪除(如同常規函式宣告)。

頂級 cv 限定符的移除不影響引數在函式內出現的型別。

template<class T>
void f(T t);
 
template<class X>
void g(const X x);
 
template<class Z>
void h(Z z, Z* zp);
 
// two different functions with the same type, but 
// within the function, t has different cv qualifications
f<int>(1);       // function type is void(int), t is int
f<const int>(1); // function type is void(int), t is const int
 
// two different functions with the same type and the same x
// (pointers to these two functions are not equal,
//  and function-local statics would have different addresses)
g<int>(1);       // function type is void(int), x is const int
g<const int>(1); // function type is void(int), x is const int
 
// only top-level cv-qualifiers are dropped:
h<const int>(1, NULL); // function type is void(int, const int*) 
                       // z is const int, zp is const int*

[編輯] 函式模板過載

函式模板和非模板函式可以過載。

非模板函式總是與具有相同型別的模板特化不同。即使具有相同的型別,不同函式模板的特化也總是彼此不同。兩個具有相同返回型別和相同引數列表的函式模板是不同的,可以透過它們的顯式模板引數列表來區分。

當在函式引數列表或返回型別中出現使用型別或非型別模板引數的表示式時,該表示式仍然是用於過載的函式模板簽名的一部分。

template<int I, int J>
A<I+J> f(A<I>, A<J>); // overload #1
 
template<int K, int L>
A<K+L> f(A<K>, A<L>); // same as #1
 
template<int I, int J>
A<I-J> f(A<I>, A<J>); // overload #2

如果兩個包含這些表示式的函式定義在ODR下是相同的,則稱涉及模板引數的兩個表示式是*等價的*,也就是說,這兩個表示式包含相同序列的標記,它們的名稱透過名稱查詢解析為相同的實體,除了模板引數可能命名不同。兩個lambda 表示式永遠不相等。(C++20 起)

template<int I, int J>
void f(A<I+J>); // template overload #1
 
template<int K, int L>
void f(A<K+L>); // equivalent to #1

在確定兩個依賴表示式是否等價時,只考慮所涉及的依賴名稱,而不考慮名稱查詢的結果。如果同一模板的多個宣告在名稱查詢結果上有所不同,則使用第一個這樣的宣告。

template<class T>
decltype(g(T())) h(); // decltype(g(T())) is a dependent type
 
int g(int);
 
template<class T>
decltype(g(T())) h()
{                  // redeclaration of h() uses earlier lookup
    return g(T()); // although the lookup here does find g(int)
}
 
int i = h<int>(); // template argument substitution fails; g(int)
                  // was not in scope at the first declaration of h()

如果兩個函式模板滿足以下條件,則認為它們是*等價的*:

  • 它們在同一作用域中宣告。
  • 它們具有相同的名稱。
  • 它們具有*等價的*模板引數列表,這意味著列表長度相同,並且對於每個對應的引數對,以下所有條件都為真:
  • 兩個引數屬於同一型別(都是型別,都是非型別,或都是模板)
  • 它們要麼都是引數包,要麼都不是。
(C++11 起)
  • 如果是非型別,它們的型別是等價的,
  • 如果是模板,它們的模板引數是等價的,
  • 如果其中一個用概念名宣告,則它們都是,並且概念名是等價的。
(C++20 起)
  • 它們返回型別和引數列表中的涉及模板引數的表示式是*等價的*。
  • 如果存在,它們在模板引數列表之後的 `requires` 子句中的表示式是等價的。
  • 如果存在,它們在函式宣告符之後的 `requires` 子句中的表示式是等價的。
(C++20 起)

涉及模板引數的兩個潛在求值(C++20 起)表示式被稱為*功能等價*,如果它們不是*等價的*,但對於任何給定的模板實參集,這兩個表示式的求值結果相同。

如果兩個函式模板*等價*,但其返回型別和引數列表中涉及模板引數的一個或多個表示式*功能等價*,則認為這兩個函式模板是*功能等價*的。

此外,如果兩個函式模板的約束指定方式不同,但它們接受並滿足相同的模板實參列表,則它們是*功能等價*但不是*等價*的。

(C++20 起)

如果程式包含功能等價但不等價的函式模板宣告,則程式是病態的;不需要診斷。

// equivalent
template<int I>
void f(A<I>, A<I+10>); // overload #1
template<int I>
void f(A<I>, A<I+10>); // redeclaration of overload #1
 
// not equivalent
template<int I>
void f(A<I>, A<I+10>); // overload #1
template<int I>
void f(A<I>, A<I+11>); // overload #2
 
// functionally-equivalent but not equivalent
// This program is ill-formed, no diagnostic required
template<int I>
void f(A<I>, A<I+10>);      // overload #1
template<int I>
void f(A<I>, A<I+1+2+3+4>); // functionally equivalent

當同一函式模板特化與多個過載函式模板匹配時(這通常是模板實參推導的結果),將執行*過載函式模板的部分排序*以選擇最佳匹配。

具體來說,部分排序發生在以下情況:

1) 對函式模板特化呼叫的過載決議
template<class X>
void f(X a);
template<class X>
void f(X* a);
 
int* p;
f(p);
2)獲取函式模板特化地址
template<class X>
void f(X a);
template<class X>
void f(X* a);
 
void (*p)(int*) = &f;
3) 當選擇作為函式模板特化的定位 delete 運算子來匹配定位 new 運算子時。
4)友元函式宣告顯式例項化顯式特化引用函式模板特化時。
template<class X>
void f(X a);  // first template f
template<class X>
void f(X* a); // second template f
template<>
void f<>(int *a) {} // explicit specialization
 
// template argument deduction comes up with two candidates:
// f<int*>(int*) and f<int>(int*)
// partial ordering selects f<int>(int*) as more specialized

非正式地,“A 比 B 更特化”意味著“A 接受的型別比 B 少”。

正式地,要確定兩個函式模板中哪一個更特化,部分排序過程首先按如下方式轉換其中一個模板:

  • 對於每個型別、非型別和模板引數,包括引數包,(C++11 起)生成一個唯一的虛構型別、值或模板並替換到模板的函式型別中。
  • 如果正在比較的兩個函式模板中只有一個是成員函式,並且該函式模板是某個類 A 的非靜態成員,則在其引數列表中插入一個新的首引數。給定 cv 作為函式模板的 cv 限定符,以及 ref 作為函式模板的 ref 限定符(C++11 起),新的引數型別是 cv A&,除非 ref&&,或者 ref 不存在且另一個模板的第一個引數具有右值引用型別,在這種情況下型別是 cv A&&(C++11 起)。這有助於運算子的排序,因為運算子既作為成員函式又作為非成員函式進行查詢。
struct A {};
 
template<class T>
struct B
{
    template<class R>
    int operator*(R&); // #1
};
 
template<class T, class R>
int operator*(T&, R&); // #2
 
int main()
{
    A a;
    B<A> b;
    b * a; // template argument deduction for int B<A>::operator*(R&) gives R=A 
           //                             for int operator*(T&, R&), T=B<A>, R=A
 
    // For the purpose of partial ordering, the member template B<A>::operator*
    // is transformed into template<class R> int operator*(B<A>&, R&);
 
    // partial ordering between 
    //     int operator*(   T&, R&)  T=B<A>, R=A
    // and int operator*(B<A>&, R&)  R=A 
    // selects int operator*(B<A>&, A&) as more specialized
}

在其中一個模板按照上述描述轉換後,使用轉換後的模板作為實參模板,另一個模板的原始模板型別作為形參模板執行模板實參推導。然後重複該過程,使用第二個模板(轉換後)作為實參,第一個模板以其原始形式作為形參。

用於確定順序的型別取決於上下文。

  • 在函式呼叫上下文中,型別是函式呼叫具有實參的函式形參型別(不考慮預設函式實參、引數包、(C++11 起)和省略號形參——請參閱下面的示例)。
  • 在呼叫使用者定義的轉換函式的上下文中,使用轉換函式模板的返回型別。
  • 在其他上下文中,使用函式模板型別。

從上述列表中,形參模板的每個型別都將被推導。在推導開始之前,形參模板的每個形參 P 和實參模板的相應實參 A 將按如下方式調整:

  • 如果 PA 之前都是引用型別,則確定哪個更具 cv 限定性(在所有其他情況下,cv 限定性在部分排序中被忽略)
  • 如果 P 是引用型別,則它被其所引用的型別替換。
  • 如果 A 是引用型別,則它被其所引用的型別替換。
  • 如果 P 具有 cv 限定,則 P 被替換為其自身的 cv 非限定版本。
  • 如果 A 具有 cv 限定,則 A 被替換為其自身的 cv 非限定版本。

在這些調整之後,按照從型別推導模板實參的方式進行 PA 的推導。

如果 P 是函式引數包,則實參模板中每個剩餘引數型別 A 將與函式引數包的宣告符 id 的型別 P 進行比較。每次比較都會推匯出由函式引數包展開的模板引數包中後續位置的模板引數。

如果 A 是從函式引數包轉換而來的,則它與引數模板的每個剩餘引數型別進行比較。

(C++11 起)

如果轉換後的模板-1 的實參 A 可用於推導模板-2 的相應形參 P,但反之不行,則此 A 在此 P/A 對推導的型別方面比 P 更特化。

如果推導在兩個方向都成功,並且原始 PA 是引用型別,則進行額外的測試。

  • 如果 A 是左值引用而 P 是右值引用,則 A 被認為比 P 更特化。
  • 如果 AP 具有更多的 cv 限定符,則 A 被認為比 P 更特化。

在所有其他情況下,對於此 P/A 對推導的型別,兩個模板均不比另一個更特化。

在雙向考慮了每個 PA 之後,如果對於所考慮的每種型別:

  • 模板-1 對於所有型別至少與模板-2 一樣特化。
  • 模板-1 對於某些型別比模板-2 更特化。
  • 模板-2 對於任何型別都不比模板-1 更特化,或者對於任何型別都不至少與模板-1 一樣特化。

那麼模板-1 比模板-2 更特化。如果切換模板順序後上述條件成立,則模板-2 比模板-1 更特化。否則,兩個模板都不比另一個更特化。

在平局的情況下,如果一個函式模板有尾隨引數包而另一個沒有,則省略引數的模板被認為比帶有空引數包的模板更特化。

(C++11 起)

如果,在考慮所有過載模板對之後,有一個模板明確地比所有其他模板更特化,則選擇該模板的特化,否則編譯失敗。

在以下示例中,虛構的引數將被稱為 U1, U2。

template<class T>
void f(T);        // template #1
template<class T>
void f(T*);       // template #2
template<class T>
void f(const T*); // template #3
 
void m()
{
    const int* p;
    f(p); // overload resolution picks: #1: void f(T ) [T = const int *]
          //                            #2: void f(T*) [T = const int]
          //                            #3: void f(const T *) [T = int]
 
    // partial ordering:
 
    // #1 from transformed #2: void(T) from void(U1*): P=T A=U1*: deduction ok: T=U1*
    // #2 from transformed #1: void(T*) from void(U1): P=T* A=U1: deduction fails
    // #2 is more specialized than #1 with regards to T
 
    // #1 from transformed #3: void(T) from void(const U1*): P=T, A=const U1*: ok
    // #3 from transformed #1: void(const T*) from void(U1): P=const T*, A=U1: fails
    // #3 is more specialized than #1 with regards to T
 
    // #2 from transformed #3: void(T*) from void(const U1*): P=T* A=const U1*: ok
    // #3 from transformed #2: void(const T*) from void(U1*): P=const T* A=U1*: fails
    // #3 is more specialized than #2 with regards to T
 
    // result: #3 is selected
    // in other words, f(const T*) is more specialized than f(T) or f(T*)
}
template<class T>
void f(T, T*);   // #1
template<class T>
void f(T, int*); // #2
 
void m(int* p)
{
    f(0, p); // deduction for #1: void f(T, T*) [T = int]
             // deduction for #2: void f(T, int*) [T = int]
 
    // partial ordering:
 
    // #1 from #2: void(T,T*) from void(U1,int*): P1=T, A1=U1: T=U1
    //                                            P2=T*, A2=int*: T=int: fails
 
    // #2 from #1: void(T,int*) from void(U1,U2*): P1=T A1=U1: T=U1
    //                                             P2=int* A2=U2*: fails
 
    // neither is more specialized w.r.t T, the call is ambiguous
}
template<class T>
void g(T);  // template #1
template<class T>
void g(T&); // template #2
 
void m()
{
    float x;
    g(x); // deduction from #1: void g(T ) [T = float]
          // deduction from #2: void g(T&) [T = float]
 
    // partial ordering:
 
    // #1 from #2: void(T) from void(U1&): P=T, A=U1 (after adjustment), ok
 
    // #2 from #1: void(T&) from void(U1): P=T (after adjustment), A=U1: ok
 
    // neither is more specialized w.r.t T, the call is ambiguous
}
template<class T>
struct A { A(); };
 
template<class T>
void h(const T&); // #1
template<class T>
void h(A<T>&);    // #2
 
void m()
{
    A<int> z;
    h(z); // deduction from #1: void h(const T &) [T = A<int>]
          // deduction from #2: void h(A<T> &) [T = int]
 
    // partial ordering:
 
    // #1 from #2: void(const T&) from void(A<U1>&): P=T A=A<U1>: ok T=A<U1>
 
    // #2 from #1: void(A<T>&) from void(const U1&): P=A<T> A=const U1: fails
 
    // #2 is more specialized than #1 w.r.t T
 
    const A<int> z2;
    h(z2); // deduction from #1: void h(const T&) [T = A<int>]
           // deduction from #2: void h(A<T>&) [T = int], but substitution fails
 
    // only one overload to choose from, partial ordering not tried, #1 is called
}

由於呼叫上下文只考慮有顯式呼叫實參的引數,所以那些函式引數包、(C++11 起)省略號引數和帶有預設實參但沒有顯式呼叫實參的引數將被忽略。

template<class T>
void f(T);         // #1
template<class T>
void f(T*, int = 1); // #2
 
void m(int* ip)
{
    int* ip;
    f(ip); // calls #2 (T* is more specialized than T)
}
template<class T>
void g(T);       // #1
template<class T>
void g(T*, ...); // #2
 
void m(int* ip)
{
    g(ip); // calls #2 (T* is more specialized than T)
}
template<class T, class U>
struct A {};
 
template<class T, class U>
void f(U, A<U, T>* p = 0); // #1
template<class U>
void f(U, A<U, U>* p = 0); // #2
 
void h()
{
    f<int>(42, (A<int, int>*)0); // calls #2
    f<int>(42);                  // error: ambiguous
}
template<class T>
void g(T, T = T()); // #1
template<class T, class... U>
void g(T, U...);    // #2
 
void h()
{
    g(42); // error: ambiguous
}
template<class T, class... U>
void f(T, U...); // #1
template<class T>
void f(T);       // #2
 
void h(int i)
{
    f(&i); // calls #2 due to the tie-breaker between parameter pack and no parameter
           // (note: was ambiguous between DR692 and DR1395)
}
template<class T, class... U>
void g(T*, U...); // #1
template<class T>
void g(T);        // #2
 
void h(int i)
{
    g(&i); // OK: calls #1 (T* is more specialized than T)
}
template<class... T>
int f(T*...);    // #1
template<class T>
int f(const T&); // #2
 
f((int*)0); // OK: selects #2; non-variadic template is more specialized than
            // variadic template (was ambiguous before DR1395 because deduction
            // failed in both directions)
template<class... Args>
void f(Args... args);        // #1
template<class T1, class... Args>
void f(T1 a1, Args... args); // #2
template<class T1, class T2>
void f(T1 a1, T2 a2);        // #3
 
f();        // calls #1
f(1, 2, 3); // calls #2
f(1, 2);    // calls #3; non-variadic template #3 is more
            // specialized than the variadic templates #1 and #2

在部分排序過程中進行模板實參推導時,如果實參未用於部分排序所考慮的任何型別,則模板引數不需要與實參匹配。

template<class T>
T f(int); // #1
template<class T, class U>
T f(U);   // #2
 
void g()
{
    f<int>(1); // specialization of #1 is explicit: T f(int) [T = int]
               // specialization of #2 is deduced:  T f(U) [T = int, U = int]
 
    // partial ordering (only considering the argument type):
 
    // #1 from #2: T(int) from U1(U2): fails
    // #2 from #1: T(U) from U1(int): ok: U=int, T unused
 
    // calls #1
}

包含模板引數包的函式模板的部分排序獨立於這些模板引數包推匯出的實引數量。

template<class...>
struct Tuple {};
 
template<class... Types>
void g(Tuple<Types...>);      // #1
template<class T1, class... Types>
void g(Tuple<T1, Types...>);  // #2
template<class T1, class... Types>
void g(Tuple<T1, Types&...>); // #3
 
g(Tuple<>());            // calls #1
g(Tuple<int, float>());  // calls #2
g(Tuple<int, float&>()); // calls #3
g(Tuple<int>());         // calls #3
(C++11 起)

為了編譯函式模板的呼叫,編譯器必須在非模板過載、模板過載和模板過載的特化之間做出選擇。

template<class T>
void f(T);      // #1: template overload
template<class T>
void f(T*);     // #2: template overload
 
void f(double); // #3: non-template overload
template<>
void f(int);    // #4: specialization of #1
 
f('a');        // calls #1
f(new int(1)); // calls #2
f(1.0);        // calls #3
f(1);          // calls #4

[編輯] 函式過載與函式特化

請注意,只有非模板和主模板過載參與過載決議。特化不是過載,不被考慮。只有在過載決議選擇最佳匹配的主函式模板後,才會檢查其特化以檢視是否存在更好的匹配。

template<class T>
void f(T);    // #1: overload for all types
template<>
void f(int*); // #2: specialization of #1 for pointers to int
template<class T>
void f(T*);   // #3: overload for all pointer types
 
f(new int(1)); // calls #3, even though specialization of #1 would be a perfect match

在排列翻譯單元的標頭檔案時,記住這條規則很重要。有關函式過載和函式特化之間相互作用的更多示例,請展開以下內容:

示例

首先考慮一些不使用實參依賴查詢的場景。為此,我們使用呼叫 (f)(t)。如ADL中所述,將函式名括在括號中會抑制實參依賴查詢。

  • f() 的多個過載在 g() 中的 *引用點* (POR) 之前宣告。
#include <iostream>
 
struct A {};
 
template<class T>
void f(T)  { std::cout << "#1\n"; } // overload #1 before f() POR
template<class T>
void f(T*) { std::cout << "#2\n"; } // overload #2 before f() POR
 
template<class T>
void g(T* t) 
{
    (f)(t); // f() POR
}
 
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
 
// Both #1 and #2 are added to the candidate list;
// #2 is selected because it is a better match.

輸出

#2


  • 一個更匹配的模板過載在 POR 之後宣告。
#include <iostream>
 
struct A {};
 
template<class T>
void f(T)  { std::cout << "#1\n"; } // #1
 
template<class T>
void g(T* t) 
{
    (f)(t); // f() POR
}
 
template<class T>
void f(T*) { std::cout << "#2\n"; } // #2
 
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
 
// Only #1 is added to the candidate list; #2 is defined after POR;
// therefore, it is not considered for overloading even if it is a better match.

輸出

#1


  • 一個更匹配的顯式模板特化在 POR 之後宣告。
#include <iostream>
 
struct A {};
 
template<class T>
void f(T)    { std::cout << "#1\n"; } // #1
 
template<class T>
void g(T* t) 
{
    (f)(t); // f() POR
}
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
 
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
 
// #1 is added to the candidate list; #3 is a better match defined after POR. The
// candidate list consists of #1 which is eventually selected. After that, the explicit 
// specialization #3 of #1 declared after POI is selected because it is a better match. 
// This behavior is governed by 14.7.3/6 [temp.expl.spec] and has nothing to do with ADL.

輸出

#3


  • 一個更匹配的模板過載在 POR 之後宣告。最佳匹配的顯式模板特化在更匹配的過載之後宣告。
#include <iostream>
 
struct A {};
 
template<class T>
void f(T)    { std::cout << "#1\n"; } // #1
 
template<class T>
void g(T* t) 
{
    (f)(t); // f() POR
}
 
template<class T>
void f(T*)   { std::cout << "#2\n"; } // #2
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
 
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
 
// #1 is the only member of the candidate list and it is eventually selected. 
// After that, the explicit specialization #3 is skipped because it actually 
// specializes #2 declared after POR.

輸出

#1


現在讓我們考慮使用實參依賴查詢的情況(即,我們使用更常見的呼叫格式f(t))。

  • 一個更匹配的模板過載在 POR 之後宣告。
#include <iostream>
 
struct A {};
 
template<class T>
void f(T)  { std::cout << "#1\n"; } // #1
 
template<class T>
void g(T* t) 
{
    f(t); // f() POR
}
 
template<class T>
void f(T*) { std::cout << "#2\n"; } // #2
 
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
 
// #1 is added to the candidate list as a result of the ordinary lookup;
// #2 is defined after POR but it is added to the candidate list via ADL lookup.
// #2 is selected being the better match.

輸出

#2


  • 一個更匹配的模板過載在 POR 之後宣告。最佳匹配的顯式模板特化在更匹配的過載之前宣告。
#include <iostream>
 
struct A {};
 
template<class T>
void f(T)    { std::cout << "#1\n"; } // #1
 
template<class T>
void g(T* t) 
{
    f(t); // f() POR
}
 
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
template<class T>
void f(T*)   { std::cout << "#2\n"; } // #2
 
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
 
// #1 is added to the candidate list as a result of the ordinary lookup;
// #2 is defined after POR but it is added to the candidate list via ADL lookup.
// #2 is selected among the primary templates, being the better match.
// Since #3 is declared before #2, it is an explicit specialization of #1.
// Hence the final selection is #2.

輸出

#2


  • 一個更匹配的模板過載在 POR 之後宣告。最佳匹配的顯式模板特化最後宣告。
#include <iostream>
 
struct A {};
 
template<class T>
void f(T)    { std::cout << "#1\n"; } // #1
 
template<class T>
void g(T* t) 
{
    f(t); // f() POR
}
 
template<class T>
void f(T*)   { std::cout << "#2\n"; } // #2
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
 
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
 
// #1 is added to the candidate list as a result of the ordinary lookup;
// #2 is defined after POR but it is added to the candidate list via ADL lookup.
// #2 is selected among the primary templates, being the better match.
// Since #3 is declared after #2, it is an explicit specialization of #2;
// therefore, selected as the function to call.

輸出

#3


只要實參是某些 C++ 基本型別,就沒有 ADL 關聯的名稱空間。因此,這些場景與上述非 ADL 示例相同。

有關過載決議的詳細規則,請參閱過載決議

[編輯] 函式模板特化

[編輯] 關鍵詞

template, extern (C++11 起)

[編輯] 缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 214 C++98 未指定部分排序的確切過程 添加了規範
CWG 532 C++98 未指定非靜態成員函式模板與非成員函式模板之間的順序
和非成員函式模板之間的順序未指定
添加了規範
CWG 581 C++98 建構函式模板的顯式特化或例項化中允許使用模板引數列表
建構函式模板的例項化被允許
已禁止
CWG 1321 C++98 不清楚第一份宣告和重新宣告中的相同依賴名稱是否等效
第一份宣告和重新宣告中的相同依賴名稱是否等效
它們是等效的
其含義與
在第一份宣告中
CWG 1395 C++11 當 A 來自包時,推導失敗,
且沒有空包的平局決勝者。
允許推導,
添加了平局決勝者
CWG 1406 C++11 為非靜態成員函式模板新增的新第一個引數的型別
與該模板的引用限定符無關
與該模板的 ref-qualifier 無關
如果 ref-qualifier 為 &&
則型別是右值引用型別。
ref-限定符為 &&
CWG 1446 C++11 為無 ref 限定符的非靜態成員函式模板新增的新第一個引數的型別是左值引用型別,即使該成員函式模板與第一個引數具有右值引用型別的函式模板進行比較。
即使該成員函式模板與第一個引數具有右值引用型別的函式模板進行比較
型別,即使該成員函式模板與
函式模板(其第一個引數具有右值引用型別)進行比較
在這種情況下,該型別是
右值引用
型別
CWG 2373 C++98 在部分排序中,新的第一個引數被新增到靜態成員函式模板的引數列表中
靜態成員函式模板的引數列表中
未新增

[編輯] 參閱