命名空间
变体
操作

模板形参和模板实参

来自 cppreference.cn
< 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. 已明确