Namespaces
Variants

Partial template specialization

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

允许为特定类别的模板参数自定义类 和变量 (since C++14) 模板。

目录

语法

template < 形参列表 > 类关键字 类头名称 < 实参列表 > 声明 (1)
template < 形参列表 > 声明说明符序列 声明符 < 实参列表 > 初始化器  (可选) (2) (自 C++14 起)

其中 class-head-name 标识先前声明的 类模板 名称 ,且 declarator 标识先前声明的 变量模板 名称 (C++14 起)

部分特化可以在其主模板可定义的任何作用域中声明(这可能与主模板定义的作用域不同,例如对 成员模板 进行类外特化的情况)。部分特化必须出现在非特化模板声明之后。

例如,

template<class T1, class T2, int I>
class A {};             // 主模板
template<class T, int I>
class A<T, T*, I> {};   // #1:当T2为T1指针时的偏特化
template<class T, class T2, int I>
class A<T*, T2, I> {};  // #2:当T1为指针时的偏特化
template<class T>
class A<int, T*, 5> {}; // #3:当T1为int类型、I为5且T2为指针时的偏特化
                        //     
template<class X, class T, int I>
class A<X, T*, I> {};   // #4:当T2为指针时的偏特化

标准库中部分特化的示例包括 std::unique_ptr ,它为数组类型提供了部分特化。

参数列表

以下限制适用于部分模板特化的 实参列表

1) 参数列表不能与非特化参数列表完全相同(必须特化某些内容):
template<class T1, class T2, int I> class B {};        // primary template
template<class X, class Y, int N> class B<X, Y, N> {}; // error

此外,特化必须比主模板更特化

template<int N, typename T1, typename... Ts> struct B;
template<typename... Ts> struct B<0, Ts...> {}; // Error: not more specialized
(since C++11)
2) 默认参数不能出现在参数列表中
3) 如果任何参数是包展开,则它必须是参数列表中的最后一个参数
4) 常量实参表达式可以使用模板形参,只要该形参至少在 非推导上下文 之外出现一次(注意目前仅 clang 和 gcc 12 支持此特性):
template<int I, int J> struct A {};
template<int I> struct A<I + 5, I * 2> {}; // error, I is not deducible
template<int I, int J, int K> struct B {};
template<int I> struct B<I, I * 2, 2> {};  // OK: first parameter is deducible
5) 常量模板实参无法特化其类型依赖于特化参数的模板形参:
template<class T, T t> struct C {}; // 主模板
template<class T> struct C<T, 1>;   // 错误:实参1的类型是T,
                                    // 该类型依赖于形参T
template<int X, int (*array_ptr)[X]> class B {}; // 主模板
int array[5];
template<int X> class B<X, &array> {}; // 错误:实参&array的类型是
                                       // int(*)[X],该类型依赖于形参X

名称查找

部分模板特化不会通过名称查找被发现。只有当主模板通过名称查找被发现时,其部分特化才会被纳入考虑。特别需要注意的是,使主模板可见的 using 声明同样会使部分特化可见:

namespace N
{
    template<class T1, class T2> class Z {}; // 主模板
}
using N::Z; // 引用主模板
namespace N
{
    template<class T> class Z<T, T*> {};     // 偏特化
}
Z<int, int*> z; // 名称查找找到 N::Z(主模板),随后使用
                // T = int 的偏特化版本

部分排序

当类 或变量 (since C++14) 模板被实例化时,若存在部分特化版本,编译器需要决定是使用主模板还是其某个部分特化版本。

1) 如果只有一个特化匹配模板参数,则使用该特化
2) 若存在多个特化匹配,则使用偏序规则确定哪个特化更为特化。若存在唯一的最特化特化,则采用该特化(若非唯一,则程序无法编译)
3) 如果没有特化匹配,则使用主模板
// given the template A as defined above
A<int, int, 1> a1;   // no specializations match, uses primary template
A<int, int*, 1> a2;  // uses partial specialization #1 (T = int, I = 1)
A<int, char*, 5> a3; // uses partial specialization #3, (T = char)
A<int, char*, 1> a4; // uses partial specialization #4, (X = int, T = char, I = 1)
A<int*, int*, 2> a5; // error: matches #2 (T = int, T2 = int*, I= 2)
                     //        matches #4 (X = int*, T = int, I = 2)
                     // neither one is more specialized than the other

非正式地讲,“A 比 B 更特化”意味着“A 接受的类型是 B 接受的类型的子集”。

正式而言,为确定偏特化之间的更特化关系,首先将每个偏特化转换为如下虚构函数模板:

  • 第一个函数模板具有与第一个偏特化相同的模板参数,且仅有一个函数参数,其类型是使用第一个偏特化中所有模板实参的类模板特化
  • 第二个函数模板具有与第二个偏特化相同的模板参数,且仅有一个函数参数,其类型是使用第二个偏特化中所有模板实参的类模板特化

函数模板随后会按照 函数模板重载 的规则进行排序。

template<int I, int J, class T> struct X {}; // 主模板
template<int I, int J>          struct X<I, J, int>
{
    static const int s = 1;
}; // 偏特化 #1
// 对应的虚构函数模板为:
// template<int I, int J> void f(X<I, J, int>); #A
template<int I>                 struct X<I, I, int>
{
    static const int s = 2;
}; // 偏特化 #2
// 对应的虚构函数模板为:
// template<int I>        void f(X<I, I, int>); #B
int main()
{
    X<2, 2, int> x; // #1 和 #2 均匹配
// 函数模板的偏序规则:
// #A 从 #B 推导:void(X<I, J, int>) 从 void(X<U1, U1, int>) 推导:成功
// #B 从 #A 推导:void(X<I, I, int>) 从 void(X<U1, U2, int>) 推导:失败
// #B 更特化
// 实际实例化的是 #2 特化版本
    std::cout << x.s << '\n'; // 输出 2
}

部分特化的成员

部分特化的成员模板参数列表和模板实参列表必须与部分特化的形参列表和实参列表相匹配。

与主模板的成员类似,它们仅在程序中被使用时才需要定义。

部分特化的成员与主模板的成员没有关联。

对部分特化的成员进行显式(完全)特化的声明方式与主模板的显式特化相同。

template<class T, int I> // 主模板
struct A
{
    void f(); // 成员声明
};
template<class T, int I>
void A<T, I>::f() {}     // 主模板成员定义
// 部分特化
template<class T>
struct A<T, 2>
{
    void f();
    void g();
    void h();
};
// 部分特化的成员
template<class T>
void A<T, 2>::g() {}
// 显式(完全)特化
// 部分特化的成员
template<>
void A<char, 2>::h() {}
int main()
{
    A<char, 0> a0;
    A<char, 2> a2;
    a0.f(); // 正确:使用主模板的成员定义
    a2.g(); // 正确:使用部分特化的成员定义
    a2.h(); // 正确:使用部分特化成员的完全特化定义
    a2.f(); // 错误:部分特化 A<T,2> 中没有 f() 的定义
            // (未使用主模板)
}

如果一个主模板是另一个类模板的成员,则其部分特化是外层类模板的成员。如果外层模板被实例化,则每个成员部分特化的声明也会被实例化(这与模板所有其他成员的声明被实例化的方式相同,但定义不会被实例化)。

如果主成员模板针对外围类模板的某个(隐式)特化进行了显式(完全)特化,则对于该外围类模板的特化,将忽略该成员模板的部分特化。

如果成员模板的局部特化针对封闭类模板的某个(隐式)特化进行了显式特化,那么对于该封闭类模板的特化,仍会考虑主成员模板及其其他局部特化。

template<class T> struct A // 外围类模板
{
    template<class T2>
    struct B {};      // 主成员模板
    template<class T2>
    struct B<T2*> {}; // 成员模板的偏特化
};
template<>
template<class T2>
struct A<short>::B {}; // 主成员模板的完全特化
                       //(将忽略偏特化版本)
A<char>::B<int*> abcip;  // 使用偏特化 T2=int
A<short>::B<int*> absip; // 使用主模板的完全特化(忽略偏特化)
A<char>::B<int> abci;    // 使用主模板

缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的C++标准。

缺陷报告 适用标准 发布时的行为 正确行为
CWG 727 C++98 不允许在类作用域中定义部分特化和完全特化 允许在任何作用域中定义
CWG 1315 C++98 模板参数不能用于除标识表达式外的常量模板实参 只要可推导,表达式即可使用
CWG 1495 C++11 涉及参数包时的规范不明确 特化必须更加特化
CWG 1711 C++14 缺少变量模板部分特化的规范 添加对变量模板的支持
CWG 1819 C++98 部分特化定义的可接受作用域 使部分特化可以在与主模板相同的作用域中声明
CWG 2330 C++14 缺少对变量模板的引用 添加对变量模板的支持

参见