命名空间
变体
操作

模板形参和模板实参

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句(循环)
for
范围 for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (于 C++17* 弃用)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
 
 
 
 

内容

[编辑] 模板形参

每个 模板 都由一个或多个模板形参参数化,这些形参在模板声明语法的 形参列表 中指示

template < 形参列表 > 声明 (1)
template < 形参列表 > requires 约束 声明 (2) (自 C++20 起)

形参列表 中的每个形参可以是

  • 非类型模板形参;
  • 类型模板形参;
  • 模板模板形参。


[编辑] 非类型模板形参

类型 名称 (可选) (1)
类型 名称 (可选) = 默认值 (2)
类型 ... 名称 (可选) (3) (自 C++11 起)
1) 非类型模板形参。
2) 带有默认模板实参的非类型模板形参。
3) 非类型模板形参包
类型 - 以下类型之一
  • 结构类型(见下文)
(自 C++17 起)
(自 C++20 起)
名称 - 非类型模板形参的名称
默认值 - 默认模板实参

结构类型是以下类型之一(可选地带有 cv 限定符,限定符会被忽略)

(自 C++11 起)
  • 所有基类和非静态数据成员都是 public 且非 mutable 的,并且
  • 所有基类和非静态数据成员的类型都是结构类型或其(可能是多维)数组。
(自 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 起)

[编辑] 类型模板形参

类型形参键 名称 (可选) (1)
类型形参键 名称 (可选) = 默认值 (2)
类型形参键 ... 名称 (可选) (3) (自 C++11 起)
类型约束 名称 (可选) (4) (自 C++20 起)
类型约束 名称 (可选) = 默认值 (5) (自 C++20 起)
类型约束 ... 名称 (可选) (6) (自 C++20 起)
类型形参键 - typenameclass 之一。在类型模板形参声明中,这些关键字之间没有区别
类型约束 - 概念的名称,或概念的名称后跟模板实参列表(在尖括号中)。无论哪种方式,概念名称都可以选择性地进行限定
名称 - 类型模板形参的名称
默认值 - 默认模板实参
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 名称,它别名化在模板实例化时提供的类型。

每个受约束形参 P,其 类型约束Q,指定概念 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 < 形参列表 > 类型形参键 名称 (可选) (1)
template < 形参列表 > 类型形参键 名称 (可选) = 默认值 (2)
template < 形参列表 > 类型形参键 ... 名称 (可选) (3) (自 C++11 起)
类型形参键 - 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 起),要么是默认的。对于函数模板,实参要么显式提供,要么从语境推导,要么是默认的。

如果实参可以解释为类型标识和表达式,则它始终被解释为类型标识,即使相应的模板形参是非类型也是如此

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 确定,如下所示

  • 如果 A 是类型为 T已转换常量表达式,则 P 的值是 A(已转换)。
  • 否则,程序是非良构的。
(直到 C++11)
  • 如果 A 是一个表达式
  • 如果 A 是类型为 T已转换常量表达式,则 P 的值是 A(已转换)。
  • 否则,程序是非良构的。
  • 否则(A 是一个花括号括起的初始化列表),引入一个临时变量 constexpr T v = A;P 的值是 v 的值。
(自 C++11 起)
(直到 C++20)
  • 如果 T 不是类类型并且 A 是一个表达式
  • 如果 A 是类型为 T已转换常量表达式,则 P 的值是 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 或求值为 null 指针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

[编辑] 类型模板实参

类型模板形参的模板实参必须是类型标识,它可以命名不完整类型

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
}

[编辑] 模板模板实参

模板模板形参的模板实参必须是标识表达式,它命名一个类模板或模板别名。

当实参是类模板时,在匹配形参时仅考虑主模板。偏特化(如果有)仅在基于此模板模板形参的特化恰好被实例化时才被考虑。

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 表达式 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 起)
特性测试宏 Std 特性
__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++ 标准。

DR 应用于 已发布行为 正确行为
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. 已明确