名稱空間
變體
操作

模板形參和模板實參

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

目錄

[編輯] 模板形參

每個模板都由一個或多個模板形參引數化,這些形參在模板宣告語法的形參列表中指出

template < parameter-list > declaration (1)
template < parameter-list > requires constraint declaration (2) (C++20 起)

形參列表中的每個形參可以是

  • 非型別模板形參;
  • 型別模板形參;
  • 模板模板形參。


[編輯] 非型別模板形參

type name (可選) (1)
type name (可選) = default (2)
type ... name (可選) (3) (C++11 起)
1) 非型別模板形參。
2) 帶預設模板實參的非型別模板形參。
3) 非型別模板形參包
型別 - 以下型別之一
  • 結構化型別 (見下文)
(C++17 起)
(C++20 起)
name - 非型別模板形參的名稱
default - 預設模板實參

“結構化型別”是以下型別之一(可帶有 cv 限定符,限定符會被忽略)

(C++11 起)
  • 所有基類和非靜態資料成員都是公共且不可變的,並且
  • 所有基類和非靜態資料成員的型別都是結構化型別或其(可能是多維)陣列。
(C++20 起)

陣列和函式型別可以在模板宣告中書寫,但它們會自動被替換為指向物件和指向函式的指標。

當非型別模板形參的名稱在類模板主體內的表示式中使用時,它是一個不可修改的純右值,除非其型別是左值引用型別,或者除非其型別是類型別(C++20 起)

形式為 class Foo 的模板形參不是型別為 Foo 的無名非型別模板形參,即使 class Foo 否則是詳盡型別說明符,並且 class Foo x; 宣告 x 的型別為 Foo

命名類型別 T 的非型別模板形參的識別符號表示型別為 const T 的靜態儲存期物件,稱為模板形參物件,它在轉換為模板形參的型別後,與相應的模板實參模板實參等價。沒有兩個模板形參物件是模板實參等價的。

struct A
{
    friend bool operator==(const A&, const A&) = default;
};
 
template<A a>
void f()
{
    &a;                       // OK
    const A& ra = a, &rb = a; // Both bound to the same template parameter object
    assert(&ra == &rb);       // passes
}
(C++20 起)

[編輯] 型別模板形參

type-parameter-key name (可選) (1)
type-parameter-key name (可選) = default (2)
type-parameter-key ... name (可選) (3) (C++11 起)
type-constraint name (可選) (4) (C++20 起)
type-constraint name (可選) = default (5) (C++20 起)
type-constraint ... name (可選) (6) (C++20 起)
type-parameter-key - typenameclass。在型別模板形參宣告中,這兩個關鍵詞沒有區別
type-constraint - 概念的名稱,或概念名稱後跟模板實參列表(在尖括號中)。無論哪種情況,概念名稱都可以是可選限定的
name - 型別模板形參的名稱
default - 預設模板實參
1) 沒有預設值的型別模板形參。
template<class T>
class My_vector { /* ... */ };
2) 帶預設值的型別模板形參。
template<class T = void>
struct My_op_functor { /* ... */ };
3) 型別模板形參包
template<typename... Ts>
class My_tuple { /* ... */ };
4) 沒有預設值的受約束型別模板形參。
template<My_concept T>
class My_constrained_vector { /* ... */ };
5) 帶預設值的受約束型別模板形參。
template<My_concept T = void>
class My_constrained_op_functor { /* ... */ };
6) 受約束型別模板形參包
template<My_concept... Ts>
class My_constrained_tuple { /* ... */ };

形參的名稱是可選的

// Declarations of the templates shown above:
template<class>
class My_vector;
template<class = void>
struct My_op_functor;
template<typename...>
class My_tuple;

在模板宣告的主體中,型別形參的名稱是一個 typedef-name,它別名為模板例項化時提供的型別。

每個受約束形參 P,其 type-constraint 指定概念 C,都會根據以下規則引入一個約束表示式 E

  • 如果 QC(不帶實參列表),
  • 如果 P 不是形參包,則 E 簡單地是 C<P>
  • 否則,P 是一個形參包,E 是一個摺疊表示式 (C<P> && ...)
  • 如果 QC<A1,A2...,AN>,則 E 分別是 C<P,A1,A2,...AN>(C<P,A1,A2,...AN> && ...)
template<typename T>
concept C1 = true;
template<typename... Ts> // variadic concept
concept C2 = true;
template<typename T, typename U>
concept C3 = true;
 
template<C1 T>         struct s1; // constraint-expression is C1<T>
template<C1... T>      struct s2; // constraint-expression is (C1<T> && ...)
template<C2... T>      struct s3; // constraint-expression is (C2<T> && ...)
template<C3<int> T>    struct s4; // constraint-expression is C3<T, int>
template<C3<int>... T> struct s5; // constraint-expression is (C3<T, int> && ...)
(C++20 起)

[編輯] 模板模板形參

template < parameter-list > type-parameter-key name (可選) (1)
template < parameter-list > type-parameter-key name (可選) = default (2)
template < parameter-list > type-parameter-key ... name (可選) (3) (C++11 起)
type-parameter-key - class typename(C++17 起)
1) 帶可選名稱的模板模板形參。
2) 帶可選名稱和預設值的模板模板形參。
3) 帶可選名稱的模板模板形參包

在模板宣告的主體中,此形參的名稱是一個模板名(需要實參才能例項化)。

template<typename T>
class my_array {};
 
// two type template parameters and one template template parameter:
template<typename K, typename V, template<typename> typename C = my_array>
class Map
{
    C<K> key;
    C<V> value;
};

[編輯] 模板形參的名字解析

模板形參的名稱不允許在其作用域(包括巢狀作用域)內重宣告。模板形參不允許與模板名稱同名。

template<class T, int N>
class Y
{
    int T;      // error: template parameter redeclared
    void f()
    {
        char T; // error: template parameter redeclared
    }
};
 
template<class X>
class X; // error: template parameter redeclared

在類模板的成員定義(出現在類模板定義之外)中,類模板成員的名稱隱藏任何包圍類模板的模板形參的名稱,但如果成員是類模板或函式模板,則不隱藏其模板形參的名稱。

template<class T>
struct A
{
    struct B {};
    typedef void C;
    void f();
 
    template<class U>
    void g(U);
};
 
template<class B>
void A<B>::f()
{
    B b; // A's B, not the template parameter
}
 
template<class B>
template<class C>
void A<B>::g(C)
{
    B b; // A's B, not the template parameter
    C c; // the template parameter C, not A's C
}

在類模板的成員定義(出現在包含類模板定義的名稱空間之外)中,模板形參的名稱隱藏此名稱空間的成員名稱。

namespace N
{
    class C {};
 
    template<class T>
    class B
    {
        void f(T);
    };
}
 
template<class C>
void N::B<C>::f(C)
{
    C b; // C is the template parameter, not N::C
}

在類模板的定義中,或在此類模板成員的定義中(出現在模板定義之外),對於每個非依賴基類,如果基類的名稱或基類成員的名稱與模板形參的名稱相同,則基類名稱或成員名稱隱藏模板形參名稱。

struct A
{
    struct B {};
    int C;
    int Y;
};
 
template<class B, class C>
struct X : A
{
    B b; // A's B
    C b; // error: A's C isn't a type name
};

[編輯] 模板實參

為了例項化模板,每個模板形參(型別、非型別或模板)都必須被相應的模板實參替換。對於類模板,實參可以是顯式提供的從初始化器推導(C++17 起),或預設的。對於函式模板,實參可以是顯式提供的、從上下文推導的,或預設的。

如果一個實參可以同時被解釋為type-id和表示式,它總是被解釋為 type-id,即使相應的模板形參是非型別的

template<class T>
void f(); // #1
 
template<int I>
void f(); // #2
 
void g()
{
    f<int()>(); // "int()" is both a type and an expression,
                // calls #1 because it is interpreted as a type
}

[編輯] 非型別模板實參

可與非型別模板形參一起使用的模板實參可以是任何顯然常量求值表示式

(C++11 前)

可與非型別模板形參一起使用的模板實參可以是任何初始化子句。如果初始化子句是表示式,它必須是顯然常量求值的。

(C++11 起)

給定非型別模板形參宣告型別T,為該形參提供的模板實參為 E

虛構宣告 T x = E; 必須滿足constexpr 變數定義的語義約束,其儲存期為靜態。

(C++26 起)

如果 T 包含佔位符型別,或者是推導類型別的佔位符,則模板形參的型別是在虛構宣告 T x = E; 中為變數 x 推匯出的型別。

如果推匯出的形參型別不是結構化型別,則程式是病態的。

對於型別使用佔位符型別的非型別模板形參包,型別是為每個模板實參獨立推導的,並且不必匹配。

(C++17 起)
template<auto n>
struct B { /* ... */ };
 
B<5> b1;   // OK: non-type template parameter type is int
B<'a'> b2; // OK: non-type template parameter type is char
B<2.5> b3; // error (until C++20): non-type template parameter type cannot be double
 
// C++20 deduced class type placeholder, class template arguments are deduced at the
// call site
template<std::array arr>
void f();
 
f<std::array<double, 8>{}>();
 
template<auto...>
struct C {};
 
C<'C', 0, 2L, nullptr> x; // OK

非型別模板形參 P 的值,其(可能推匯出的)(C++17 起)型別為 T,從其模板實參 A 按如下方式確定

(C++11 前)
  • 如果 A 是表示式
  • 否則(A 是括號括起來的初始化列表),引入一個臨時變數 constexpr T v = A;P 的值是 v 的值。
(C++11 起)
(C++20 前)
  • 如果 T 不是類型別且 A 是表示式
  • 否則(T 是類型別或 A 是括號括起來的初始化列表),引入一個臨時變數 constexpr T v = A;
  • vP生命週期在初始化後立即結束。
  • 如果 P 的初始化滿足以下任何條件,則程式是病態的
  • 否則,P 的值是 v 的值。
(C++20 起)
template<int i>
struct C { /* ... */ };
 
C<{42}> c1; // OK
 
template<auto n>
struct B { /* ... */ };
 
struct J1
{
    J1* self = this;
};
 
B<J1{}> j1; // error: initialization of the template parameter object
            //        is not a constant expression
 
struct J2
{
    J2 *self = this;
    constexpr J2() {}
    constexpr J2(const J2&) {}
};
 
B<J2{}> j2; // error: the template parameter object is not
            //        template-argument-equivalent to introduced temporary

例項化具有非型別模板形參的模板時適用以下限制

  • 對於整型和算術型別,例項化期間提供的模板實參必須是模板形參型別的轉換後的常量表達式(因此適用某些隱式轉換)。
  • 對於指向物件的指標,模板實參必須指定具有靜態儲存期連結(內部或外部)的完整物件的地址,或一個求值為適當空指標std::nullptr_t(C++11 起)值的常量表達式。
  • 對於指向函式的指標,有效實參是指向具有連結的函式的指標(或求值為空指標值的常量表達式)。
  • 對於左值引用形參,例項化時提供的實參不能是臨時物件、無名左值或無連結的具名左值(換句話說,實參必須具有連結)。
  • 對於指向成員的指標,實參必須是表示為 &Class::Member 的成員指標,或一個求為空指標std::nullptr_t(C++11 起)值的常量表達式。

特別是,這意味著字串字面量、陣列元素的地址和非靜態成員的地址不能用作模板實參來例項化相應的非型別模板形參為指向物件的指標的模板。

(C++17 前)

引用或指標型別的非型別模板形參,以及類型別非型別模板形參及其子物件中引用或指標型別的非靜態資料成員(C++20 起)不能引用/是以下物件的地址:

  • 臨時物件(包括在引用初始化期間建立的物件);
  • 字串字面量
  • typeid 的結果;
  • 預定義變數 __func__
  • 或上述之一的子物件(包括非靜態類成員、基子物件或陣列元素)(C++20 起)
(C++17 起)
template<const int* pci>
struct X {};
 
int ai[10];
X<ai> xi; // OK: array to pointer conversion and cv-qualification conversion
 
struct Y {};
 
template<const Y& b>
struct Z {};
 
Y y;
Z<y> z;   // OK: no conversion
 
template<int (&pa)[5]>
struct W {};
 
int b[5];
W<b> w;   // OK: no conversion
 
void f(char);
void f(int);
 
template<void (*pf)(int)>
struct A {};
 
A<&f> a;  // OK: overload resolution selects f(int)
template<class T, const char* p>
class X {};
 
X<int, "Studebaker"> x1; // error: string literal as template-argument
 
template<int* p>
class X {};
 
int a[10];
 
struct S
{
    int m;
    static int s;
} s;
 
X<&a[2]> x3; // error (until C++20): address of array element
X<&s.m> x4;  // error (until C++20): address of non-static member
X<&s.s> x5;  // OK: address of static member
X<&S::s> x6; // OK: address of static member
 
template<const int& CRI>
struct B {};
 
B<1> b2;     // error: temporary would be required for template argument
int c = 1;
B<c> b1;     // OK

[編輯] 型別模板實參

型別模板形參的模板實參必須是型別 ID,它可以命名不完整型別

template<typename T>
class X {}; // class template
 
struct A;            // incomplete type
typedef struct {} B; // type alias to an unnamed type
 
int main()
{
    X<A> x1;  // OK: 'A' names a type
    X<A*> x2; // OK: 'A*' names a type
    X<B> x3;  // OK: 'B' names a type
}

[編輯] 模板模板實參

模板模板形參的模板實參必須是命名類模板或模板別名的id-expression

當實參是類模板時,只有主模板在匹配形參時被考慮。如果有部分特化,則只有在基於此模板模板形參的特化被例項化時才被考慮。

template<typename T> // primary template
class A { int x; };
 
template<typename T> // partial specialization
class A<T*> { long x; };
 
// class template with a template template parameter V
template<template<typename> class V>
class C
{
    V<int> y;  // uses the primary template
    V<int*> z; // uses the partial specialization
};
 
C<A> c; // c.y.x has type int, c.z.x has type long

為了將模板模板實參 A 匹配到模板模板形參 PP 必須至少與 A 一樣特化(見下文)。如果 P 的形參列表包含形參包,則 A 的模板形參列表中的零個或多個模板形參(或形參包)將由它匹配。(C++11 起)

形式上,如果給定以下對兩個函式模板的重寫,則模板模板形參 P 至少與模板模板實參 A 一樣特化,即根據函式模板的部分排序規則,對應於 P 的函式模板至少與對應於 A 的函式模板一樣特化。給定一個虛構的類模板 X,其模板形參列表與 A 相同(包括預設實參)

  • 兩個函式模板分別具有與 PA 相同的模板形參。
  • 每個函式模板都有一個單一的函式形參,其型別是 X 的特化,模板實參對應於相應函式模板的模板形參,其中,對於函式模板的模板形參列表中的每個模板形參 PP,形成相應的模板實參 AA如果 PP 宣告一個形參包,則 AA 是包展開 PP...;否則,(C++11 起) AA 是 id-expression PP

如果重寫產生無效型別,則 P 不至少與 A 一樣特化。

template<typename T>
struct eval;                     // primary template
 
template<template<typename, typename...> class TT, typename T1, typename... Rest>
struct eval<TT<T1, Rest...>> {}; // partial specialization of eval
 
template<typename T1> struct A;
template<typename T1, typename T2> struct B;
template<int N> struct C;
template<typename T1, int N> struct D;
template<typename T1, typename T2, int N = 17> struct E;
 
eval<A<int>> eA;        // OK: matches partial specialization of eval
eval<B<int, float>> eB; // OK: matches partial specialization of eval
eval<C<17>> eC;         // error: C does not match TT in partial specialization
                        // because TT's first parameter is a
                        // type template parameter, while 17 does not name a type
eval<D<int, 17>> eD;    // error: D does not match TT in partial specialization
                        // because TT's second parameter is a
                        // type parameter pack, while 17 does not name a type
eval<E<int, float>> eE; // error: E does not match TT in partial specialization
                        // because E's third (default) parameter is a non-type

在採用P0522R0之前,A 的每個模板形參都必須與 P 的相應模板形參完全匹配。這阻礙了許多合理的模板實參被接受。

儘管它很早就被指出(CWG#150),但在它被解決時,這些更改已應用於 C++17 工作草案,並且該解決方案成為事實上的 C++17 功能。許多編譯器預設停用它

  • GCC 預設在 C++17 之前的所有語言模式中停用它,只能透過在這些模式中設定編譯器標誌來啟用它。
  • Clang 預設在所有語言模式中停用它,只能透過設定編譯器標誌來啟用它。
  • Microsoft Visual Studio 將其視為正常的 C++17 功能,並且僅在 C++17 及更高版本的語言模式中啟用它(即在 C++14 語言模式中不支援,C++14 是預設模式)。
template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };
template<class... Types> class C { /* ... */ };
 
template<template<class> class P> class X { /* ... */ };
X<A> xa; // OK
X<B> xb; // OK after P0522R0
         // Error earlier: not an exact match
X<C> xc; // OK after P0522R0
         // Error earlier: not an exact match
 
template<template<class...> class Q> class Y { /* ... */ };
Y<A> ya; // OK
Y<B> yb; // OK
Y<C> yc; // OK
 
template<auto n> class D { /* ... */ };   // note: C++17
template<template<int> class R> class Z { /* ... */ };
Z<D> zd; // OK after P0522R0: the template parameter
         // is more specialized than the template argument
 
template<int> struct SI { /* ... */ };
template<template<auto> class> void FA(); // note: C++17
FA<SI>(); // Error

[編輯] 預設模板實參

預設模板實參在形參列表中用 = 符號指定。預設值可以為任何型別的模板形參(型別、非型別或模板)指定,但不能為形參包指定(C++11 起)

如果為主類模板、主變數模板(C++14 起)或別名模板的模板形參指定了預設值,則每個後續模板形參都必須具有預設實參,除了最後一個可以是模板形參包(C++11 起)。在函式模板中,預設值之後的形參沒有限制,並且形參包之後可以有更多型別形參,僅當它們具有預設值或可以從函式實參推導時(C++11 起)

不允許使用預設形參

(C++11 前)

在友元函式模板宣告上,預設模板實參僅當宣告是定義並且此函式在當前翻譯單元中沒有其他宣告時才允許。

(C++11 起)

在宣告中出現的預設模板引數的合併方式與預設函式引數類似。

template<typename T1, typename T2 = int> class A;
template<typename T1 = int, typename T2> class A;
 
// the above is the same as the following:
template<typename T1 = int, typename T2 = int> class A;

但在同一作用域中,同一引數不能被賦予兩次預設引數。

template<typename T = int> class X;
template<typename T = int> class X {}; // error

當解析非型別模板引數的預設模板引數時,第一個非巢狀的 > 被視為模板引數列表的結束,而不是大於運算子。

template<int i = 3 > 4>   // syntax error
class X { /* ... */ };
 
template<int i = (3 > 4)> // OK
class Y { /* ... */ };

模板模板引數的模板引數列表可以有自己的預設引數,這些預設引數僅在模板模板引數本身的作用域內有效。

// class template, with a type template parameter with a default
template<typename T = float>
struct B {};
 
// template template parameter T has a parameter list, which
// consists of one type template parameter with a default
template<template<typename = float> typename T>
struct A
{
    void f();
    void g();
};
 
// out-of-body member function template definitions
 
template<template<typename TT> class T>
void A<T>::f()
{
    T<> t; // error: TT has no default in scope
}
 
template<template<typename TT = char> class T>
void A<T>::g()
{
    T<> t; // OK: t is T<char>
}

預設模板引數中使用的名稱的成員訪問是在宣告處檢查的,而不是在使用點檢查的。

class B {};
 
template<typename T>
class C
{
protected:
    typedef T TT;
};
 
template<typename U, typename V = typename U::TT>
class D: public U {};
 
D<C<B>>* d; // error: C::TT is protected

當需要預設引數的值時,預設模板引數會隱式例項化,除非該模板用於命名函式。

template<typename T, typename U = int>
struct S {};
 
S<bool>* p; // The default argument for U is instantiated at this point
            // the type of p is S<bool, int>*
(C++14 起)

[編輯] 模板實參等價性

模板實參等價性用於確定兩個模板識別符號是否相同。

如果兩個值型別相同且滿足以下任一條件,則它們是模板實參等價的

  • 它們是整型或列舉型別,且它們的值相同。
  • 它們是指標型別,且它們具有相同的指標值。
  • 它們是指向成員的指標型別,且它們引用相同的類成員,或都是空成員指標值。
  • 它們是左值引用型別,且它們引用相同的物件或函式。
(C++11 起)
  • 它們是浮點型別,且它們的值相同。
  • 它們是陣列型別(在這種情況下,陣列必須是某個類/聯合的成員物件),且它們的相應元素是模板實參等價的。
  • 它們是聯合型別,且它們要麼都沒有活動成員,要麼具有相同的活動成員,並且它們的活動成員是模板實參等價的。
  • 它們是 lambda 閉包型別。
  • 它們是非聯合類型別,且它們的相應直接子物件和引用成員是模板實參等價的。
(C++20 起)

[編輯] 注意

在模板引數中,型別約束可以用於型別和非型別引數,具體取決於是否存在 auto

template<typename>
concept C = true;
 
template<C,     // type parameter 
         C auto // non-type parameter
        >
struct S{};
 
S<int, 0> s;


(C++20 起)
功能測試宏 標準 特性
__cpp_nontype_template_parameter_auto 201606L (C++17) 使用 auto 宣告非型別模板引數
__cpp_template_template_args 201611L (c++17)
(DR)
模板模板實參的匹配
__cpp_nontype_template_args 201411L (C++17) 允許對所有非型別模板實參進行常量求值
201911L (C++20) 非型別模板引數中的類型別和浮點型別

[編輯] 示例

#include <array>
#include <iostream>
#include <numeric>
 
// simple non-type template parameter
template<int N>
struct S { int a[N]; };
 
template<const char*>
struct S2 {};
 
// complicated non-type example
template
<
    char c,             // integral type
    int (&ra)[5],       // lvalue reference to object (of array type)
    int (*pf)(int),     // pointer to function
    int (S<10>::*a)[10] // pointer to member object (of type int[10])
>
struct Complicated
{
    // calls the function selected at compile time
    // and stores the result in the array selected at compile time
    void foo(char base)
    {
        ra[4] = pf(c - base);
    }
};
 
//  S2<"fail"> s2;        // error: string literal cannot be used
    char okay[] = "okay"; // static object with linkage
//  S2<&okay[0]> s3;      // error: array element has no linkage
    S2<okay> s4;          // works
 
int a[5];
int f(int n) { return n; }
 
// C++20: NTTP can be a literal class type
template<std::array arr>
constexpr
auto sum() { return std::accumulate(arr.cbegin(), arr.cend(), 0); }
 
// C++20: class template arguments are deduced at the call site
static_assert(sum<std::array<double, 8>{3, 1, 4, 1, 5, 9, 2, 6}>() == 31.0);
// C++20: NTTP argument deduction and CTAD
static_assert(sum<std::array{2, 7, 1, 8, 2, 8}>() == 28);
 
int main()
{
    S<10> s; // s.a is an array of 10 int
    s.a[9] = 4;
 
    Complicated<'2', a, f, &S<10>::a> c;
    c.foo('0');
 
    std::cout << s.a[9] << a[4] << '\n';
}

輸出

42

[編輯] 缺陷報告

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

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 150
(P0522R0)
C++98 模板模板實參必須與模板模板引數列表完全匹配
列表完全匹配
更專業
也允許
CWG 184 C++98 模板模板引數的模板引數是否允許擁有預設引數是未指定的
引數是否允許擁有預設引數是未指定的
添加了規範
CWG 354 C++98 空指標值不能作為非型別模板實參 允許
CWG 1398 C++11 非型別模板實參不能擁有型別 std::nullptr_t 允許
CWG 1570 C++98 非型別模板實參可以指定子物件的地址 不允許
CWG 1922 C++98 不清楚一個類模板,其名稱是
注入類名,是否可以使用先前宣告中的預設引數
允許
CWG 2032 C++14 對於變數模板,在帶有預設引數的模板引數之後沒有對模板引數的限制
引數之後沒有對模板的限制
應用相同的限制
如同類模板
和別名模板
CWG 2542 C++20 不清楚閉包型別是否是結構化的 它不是結構化的
CWG 2845 C++20 閉包型別不是結構化的 它是結構化的
如果無捕獲
P2308R1 C++11
C++20
1. 列表初始化不被允許用於
非型別模板實參 (C++11)
2. 不清楚類型別的非型別模板
引數如何初始化 (C++20)
1. 允許
2. 已明確