部分模板特化

来自cppreference.com
< cpp‎ | language

允许为给定类别的模板实参定制类模板。

目录

[编辑] 语法

template < parameter-list > class-key class-head-name < argument-list > declaration

其中 class-head-name 标识先前声明的类模板。此声明必须在与它所特化的初等模板定义相同的命名空间中,或对于成员模板,在相同的类作用域中。

例如,

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 ,它对数组类型有部分特化。

[编辑] 实参列表

下列限制应用于部分模板特化的 argument-list

1) 实参列表不能与非特化的实参列表等同(它必须特化内容)
template<class T1, class T2, int I> class B {}; // 初等模板
template<class X, class Y, int N> class B<X,Y,N> {}; // 错误

还有,特化必须比初等模板更特化

template<int N, typename T1, typename... Ts> struct B;
template<typename... Ts> struct B<0, Ts...> { }; // 错误:没有更特化
(C++14 起)
2) 默认实参不能出现于实参列表
3) 若任何实参是包展开,则它必须是列表中的最末实参
4) 非类型实参表达式不能使用模板形参之名,除了它恰巧是模板形参名称的情况 (C++14 前)非类型实参表达式能用模板形参,只要形参至少一次出现于非推导语境之外 (C++14 起)
template <int I, int J> struct A {};
template <int I> struct A<I+5, I*2> {}; // 错误: I 不可推导
 
template <int I, int J, int K> struct B {};
template <int I> struct B<I, I*2, 2> {};  // OK :首个形参可推导
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 的部分特化

[编辑] 偏序

实例化类模板,且有部分特化可用时,编译器必须决定是继续使用初等模板还是使用其部分特化之一。

1) 若只有一个特化匹配模板实参,则使用该特化
2) 若多于一个特化匹配,则用偏序规则确定哪个特化更加特化。使用最特化的特化,若它唯一(若它不唯一,则程序无法编译)
3) 若无匹配的特化,则使用初等模板
// 给定定义于上的模板 A
A<int, int, 1> a1;   // 无匹配的特化,用初等模板
A<int, int*, 1> a2;  // 用部分特化 #1 , (T=int, I=1)
A<int, char*, 5> a3; // 用部分特化 #3 , (T=char)
A<int, char*, 1> a4; // 用部分特化 #4 , (X=int, T=char, I=1)
A<int*, int*, 2> a5; // 错误:匹配 #2 (T=int, T2=int*, I=2)
                     //      匹配 #4 (X=int*, T=int, I=2)
                     // 无一者比另一者更特化

非正式而言“ 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
// #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
// #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>) :推导 ok
// #B 自 #A : void(X<I,I,int>) 自 void(X<U1, U2, int>) :推导失败
// #B 更特化
// #2 is the specialization that is instantiated
    std::cout << x.s << '\n'; // prints 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(); // OK ,用初等模板的成员定义
    a2.g(); // OK ,用部分特化的成员定义
    a2.h(); // OK ,用部分特化的成员的全特化定义
    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++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 1315 C++14 模板形参不能用于异于 id 表达式的非类型参数表达式 只要能推导,表达式就 OK