Namespaces
Variants

Template parameters

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

每个 模板 都由一个或多个模板参数进行参数化。

每个在 模板参数列表 中的参数(参见 模板声明语法 )都属于以下类别之一:

  • 常量模板参数
  • 类型模板参数
  • 模板模板参数

目录

常量模板参数

亦称为 非类型模板参数 (参见 下文 )。

类型 名称  (可选) (1)
类型 名称  (可选) = 默认值 (2)
类型 ... 名称  (可选) (3) (自 C++11 起)
type - 以下类型之一:
  • 结构类型(见下文)
(C++17 起)
(C++20 起)
name - 常量模板参数的名称
default - 默认模板实参
1) 常量模板参数。
2) 带有默认模板实参的常量模板形参。
3) 一个常量模板 parameter pack


一个 结构类型 是以下类型之一(可选地带有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; // 均绑定到同一模板参数对象
    assert(&ra == &rb);       // 通过
}
(since C++20)

类型模板参数

类型参数关键字 名称  (可选) (1)
类型参数关键字 名称  (可选) = 默认值 (2)
类型参数关键字 ... 名称  (可选) (3) (C++11 起)
类型约束 名称  (可选) (4) (C++20 起)
类型约束 名称  (可选) = 默认值 (5) (C++20 起)
类型约束 ... 名称  (可选) (6) (C++20 起)
类型参数关键字 - 可以是 typename class 。在类型模板参数声明中这两个关键字没有区别
类型约束 - 可以是 concept 的名称,也可以是后跟模板参数列表(置于尖括号中)的concept名称。无论哪种情况,concept名称都可以选择性地进行限定
名称 - 类型模板参数的名称
默认值 - 默认模板参数
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 { /* ... */ };


参数名称是可选的:

// 上述模板的声明:
template<class>
class My_vector;
template<class = void>
struct My_op_functor;
template<typename...>
class My_tuple;

在模板声明体内,类型参数的名称是一个typedef名称,它别名化模板实例化时提供的类型。

每个具有类型约束 type-constraint 为Q(指定概念 C )的受约束参数 P ,会根据以下规则引入一个 约束表达式 E

  • 如果 Q C (不带实参列表),
  • 如果 P 不是参数包, E 即为 C<P>
  • 否则 P 是参数包, E 是折叠表达式 (C<P> && ...)
  • 如果 Q C<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> && ...)
(since 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 起)
类型形参关键字 - class typename (自 C++17 起)
1) 带有可选名称的模板模板参数。
2) 带有可选名称和默认值的模板模板参数。
3) 一个模板模板 参数包 ,可带有可选名称。


在模板声明的主体中,此参数的名称是一个模板名(需要提供实参才能实例化)。

template<typename T>
class my_array {};
// 两个类型模板参数与一个模板模板参数:
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;      // 错误:模板参数被重复声明
    void f()
    {
        char T; // 错误:模板参数被重复声明
    }
};
template<class X>
class X; // 错误:模板参数被重复声明

在类模板定义外部出现的类模板成员定义中,类模板成员名称会隐藏任何外层类模板的模板参数名称,但不会隐藏该成员自身的模板参数(如果该成员是类模板或函数模板)。

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的B类型,而非模板参数
}
template<class B>
template<class C>
void A<B>::g(C)
{
    B b; // 指代A的B类型,而非模板参数
    C c; // 指代模板参数C,而非A的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 是模板参数,而非 N::C
}

在类模板的定义中,或出现在模板定义外部的此类模板成员的定义中,对于每个非 依赖 基类,若基类名称或基类成员名称与模板参数名称相同,则基类名称或成员名称会隐藏模板参数名称。

struct A
{
    struct B {};
    int C;
    int Y;
};
template<class B, class C>
struct X : A
{
    B b; // A的B
    C b; // 错误:A的C不是类型名称
};

默认模板参数

默认模板实参在参数列表中的 = 符号后指定。可以为任何类型的模板参数(类型、常量或模板)指定默认值 ,但不能为参数包指定 (since C++11)

若为主类模板 、主变量模板 (C++14 起) 或别名模板的模板参数指定了默认值,则每个后续模板参数都必须具有默认实参 ,但最后一个参数可以是模板参数包除外 (C++11 起) 。在函数模板中,跟在默认参数后的参数不受限制 ,且参数包后仅可跟随具有默认值或可从函数实参推导的类型参数 (C++11 起)

不允许使用默认参数

(C++11 前)

在友元函数模板声明中,仅当该声明为定义且此翻译单元中未出现此函数的其他声明时,才允许使用默认模板参数。

(since C++11)

出现在声明中的默认模板参数的合并方式与默认函数参数类似:

template<typename T1, typename T2 = int> class A;
template<typename T1 = int, typename T2> class A;
// 以上代码等价于:
template<typename T1 = int, typename T2 = int> class A;

但同一参数不能在相同作用域中多次指定默认参数:

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

在解析常量模板参数的默认模板实参时,第一个非嵌套的 > 会被视为模板参数列表的结束符,而非大于运算符:

template<int i = 3 > 4>   // 语法错误
class X { /* ... */ };
template<int i = (3 > 4)> // 正确
class Y { /* ... */ };

模板模板参数的模板形参列表可以拥有其自身的默认实参,这些默认实参仅在模板模板参数本身处于作用域内时生效:

// 类模板,带有默认值的类型模板参数
template<typename T = float>
struct B {};
// 模板模板参数T拥有参数列表,其中
// 包含一个带有默认值的类型模板参数
template<template<typename = float> typename T>
struct A
{
    void f();
    void g();
};
// 体外成员函数模板定义
template<template<typename TT> class T>
void A<T>::f()
{
    T<> t; // 错误:TT在作用域内无默认值
}
template<template<typename TT = char> class T>
void A<T>::g()
{
    T<> t; // 正确:t的类型是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; // 错误:C::TT 是受保护的

默认模板参数会在需要该默认参数值时被隐式实例化,除非该模板用于命名函数:

template<typename T, typename U = int>
struct S {};
S<bool>* p; // 此时 U 的默认参数被实例化
            // p 的类型是 S<bool, int>*
(since C++14)

注释

在C++26之前,标准术语中将常量模板参数称为非类型模板参数。该术语通过 P2841R6 / PR#7587 进行了更改。

在模板参数中,类型约束可用于类型参数和常量参数,具体取决于是否出现 auto

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


(since C++20)
功能测试宏 标准 功能特性
__cpp_nontype_template_parameter_auto 201606L (C++17) 使用 auto 声明 常量模板参数
__cpp_nontype_template_args 201411L (C++17) 允许对所有 常量模板实参 进行常量求值
201911L (C++20) 常量模板参数 中的类类型和浮点类型

示例

#include <array>
#include <iostream>
#include <numeric>
// 简单常量模板参数
template<int N>
struct S { int a[N]; };
template<const char*>
struct S2 {};
// 复杂常量示例
template
<
    char c,             // 整型
    int (&ra)[5],       // 对象左值引用(数组类型)
    int (*pf)(int),     // 函数指针
    int (S<10>::*a)[10] // 成员对象指针(类型为 int[10])
>
struct Complicated
{
    // 调用编译时选择的函数
    // 并将结果存储在编译时选择的数组中
    void foo(char base)
    {
        ra[4] = pf(c - base);
    }
};
//  S2<"fail"> s2;        // 错误:字符串字面量不可用
    char okay[] = "okay"; // 具有链接的静态对象
//  S2<&okay[0]> s3;      // 错误:数组元素无链接
    S2<okay> s4;          // 正常工作
int a[5];
int f(int n) { return n; }
// C++20:NTTP 可以是字面量类类型
template<std::array arr>
constexpr
auto sum() { return std::accumulate(arr.cbegin(), arr.cend(), 0); }
// C++20:类模板参数在调用点推导
static_assert(sum<std::array<double, 8>{3, 1, 4, 1, 5, 9, 2, 6}>() == 31.0);
// C++20:NTTP 参数推导与 CTAD
static_assert(sum<std::array{2, 7, 1, 8, 2, 8}>() == 28);
int main()
{
    S<10> s; // s.a 是包含 10 个整数的数组
    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 184 C++98 未说明模板模板参数的模板形参
是否允许拥有默认实参
已添加规范说明
CWG 1922 C++98 不明确类模板名称作为注入类名时
是否可使用先前声明的默认实参
允许使用
CWG 2032 C++14 对于变量模板,带默认实参的模板形参
之后的模板形参没有限制
应用与类模板和别名模板
相同的限制
CWG 2542 C++20 不明确闭包类型是否为结构类型 不是结构类型
CWG 2845 C++20 闭包类型不是结构类型 若无捕获则为结构类型