显式(全)模板特化

来自cppreference.com
< cpp‎ | language

允许对给定的模板实参集定制模板代码。

语法

template <> 声明

以下任何一项均可以完全特化:

  1. 函数模板
  2. 类模板
  3. 变量模板 (C++14 起)
  4. 类模板的成员函数
  5. 类模板的静态数据成员
  6. 类模板的成员类
  7. 类模板的成员枚举
  8. 类或类模板的成员类模板
  9. 类或类模板的成员函数模板

例如,

#include <type_traits>
 
template<typename T> // 主模板
struct is_void : std::false_type {};
template<>           // 对 T = void 的显式特化
struct is_void<void> : std::true_type {};
 
int main()
{
    static_assert(is_void<char>::value == false,
        "对于任何非 void 的类型 T,该类均派生自 false_type");
    static_assert(is_void<void>::value == true,
        "但当 T 是 void 时,类派生自 true_type");
}

细节

显式特化可以在任何可以定义它的主模板的作用域中声明(可以与定义它的主模板的作用域不同;例如同成员模板的类外特化)。显式特化必须在非特化模板声明后出现。

namespace N
{
    template<class T> // 主模板
    class X { /*...*/ };
    template<>        // 同命名空间中的特化
    class X<int> { /*...*/ };
 
    template<class T> // 主模板
    class Y { /*...*/ };
    template<>        // 对 double 特化的前置声明
    class Y<double>;
}
 
template<> // OK:相同命名空间中的特化
class N::Y<double> { /*...*/ };

特化必须在导致隐式实例化的首次使用之前,在每个发生这种使用的翻译单元中声明:

class String {};
 
template<class T>
class Array { /*...*/ };
 
template<class T> // 主模板
void sort(Array<T>& v) { /*...*/ }
 
void f(Array<String>& v)
{
    sort(v); // 使用初等模板 sort() 隐式实例化 sort(Array<String>&)
}
 
template<> // 错误:sort(Array<String>) 的显式特化在隐式实例化之后出现
void sort<String>(Array<String>& v);

只有声明没有定义的模板特化可以像其他不完整类型一样使用(例如可以使用到它的指针和引用):

template<class T> // 主模板
class X;
template<>        // 特化(声明,不定义)
class X<int>;
 
X<int>* p; // OK:指向不完整类型的指针
X<int> x;  // 错误:不完整类型的对象

函数模板和变量模板 (C++14 起)的显式特化是否为 inline/constexpr (C++11 起)/constinit/consteval (C++20 起) 只与显式特化自身有关,主模板的声明是否带有对应说明符对它没有影响。模板声明中出现的属性在它的显式特化中也没有效果: (C++11 起)

template<class T>
void f(T) { /* ... */ }
template<>
inline void f<>(int) { /* ... */ } // OK,内联
 
template<class T>
inline T g(T) { /* ... */ }
template<>
int g<>(int) { /* ... */ }         // OK,没有内联
 
template<typename>
[[noreturn]] void h([[maybe_unused]] int i);
template<> void h<int>(int i)
{
    // [[noreturn]] 没有效果,但是 [[maybe_unused]] 有效果
}

函数模板的显式特化

当特化函数模板时,如果模板实参推导能通过函数实参提供,那么就可以忽略它的模板实参:

template<class T>
class Array { /*...*/ };
 
template<class T> // 主模板
void sort(Array<T>& v);
template<>        // 对 T = int 的特化
void sort(Array<int>&);
 
// 不需要写成 template<> void sort<int>(Array<int>&);

与某个特化带有相同名字和相同形参列表的函数不是特化(见函数模板中的模板重载)。

不能在函数模板,成员函数模板,以及隐式实例化类时的类模板的成员函数的显式特化中指定默认函数实参

显式特化不能是友元声明

特化的成员

在类体外定义显式特化的类模板的成员时,不使用 template<> 语法,除非它是某个被特化为类模板的显式特化的成员类模板的成员,因为在其他情况下语法会要求这种定义以嵌套模板所要求的 template<形参> 开始:

template<typename T>
struct A
{
    struct B {};      // 成员类
 
    template<class U> // 成员类模板
    struct C {};
};
 
template<> // 特化
struct A<int>
{
    void f(int); // 特化的成员函数
};
// template<> 不会用于特化的成员
void A<int>::f(int) { /* ... */ }
 
template<> // 成员类的特化
struct A<char>::B
{
    void f();
};
// template<> 也不会用于特化的成员类的成员
void A<char>::B::f() { /* ... */ }
 
template<> // 成员类模板的特化
template<class U>
struct A<char>::C
{
    void f();
};
 
// template<> 会用于定义被特化为类模板的显式特化的成员类模板的成员
template<>
template<class U>
void A<char>::C<U>::f() { /* ... */ }

模板的静态数据成员的显式特化在它的声明包含初始化器时是定义;否则它是声明。这些定义必须用花括号进行默认初始化:

template<>
X Q<int>::x;   // 静态成员的声明
template<>
X Q<int>::x(); // 错误:函数声明
template<>
X Q<int>::x{}; // 静态成员的默认初始化定义

类模板的成员或成员模板可对于类模板的隐式实例化显式特化,即使成员或成员模板定义于类模板定义中。

template<typename T>
struct A
{
    void f(T);         // 成员,在主模板中声明
 
    void h(T) {}       // 成员,在主模板中定义
 
    template<class X1> // 成员模板
    void g1(T, X1);
 
    template<class X2> // 成员模板
    void g2(T, X2);
};
 
// 成员的特化
template<>
void A<int>::f(int);
 
// 成员特化 OK,即使在类中定义
template<>
void A<int>::h(int) {}
 
// 类外成员模板定义
template<class T>
template<class X1>
void A<T>::g1(T, X1) {}
 
// 成员模板特化
template<>
template<class X1>
void A<int>::g1(int, X1);
 
// 成员模板特化
template<>
template<>
void A<int>::g2<char>(int, char); // 对于 X2 = char
 
// 同上,用模板实参推导(X1 = char)
template<> 
template<>
void A<int>::g1(int, char);

成员或成员模板可以在多个外围类模板内嵌套。在这种成员的显式特化中,对每个显式特化的外围类模板都有一个 template<>

template<class T1>
struct A
{
    template<class T2>
    struct B
    {
        template<class T3>
        void mf();
    };
};
 
template<>
struct A<int>;
 
template<>
template<>
struct A<char>::B<double>;
 
template<>
template<>
template<>
void A<char>::B<char>::mf<double>();

在这种嵌套声明中,某些层次可保留不特化(但如果它的外围类没有被特化,那么就不能特化类成员模板)。这种层次的每个声明都需要 template<实参>,因为这种特化自身也是模板:

template <class T1>
class A
{
    template<class T2>
    class B
    {
        template<class T3> // 成员模板
        void mf1(T3);
 
        void mf2();        // 非模板成员
    };
};
 
// 特化
template<>        // 对于特化的 A
template<class X> // 对于未特化的 B
class A<int>::B
{
    template <class T> void mf1(T);
};
 
// 特化
template<>        // 对于特化的 A
template<>        // 对于特化的 B
template<class T> // 对于未特化的 mf1
void A<int>::B<double>::mf1(T t) {}
 
// 错误:B<double> 被特化而且是成员模板,所以它外围的 A 也必须特化
template<class Y>
template<>
void A<Y>::B<double>::mf2() {}

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 531 C++98 未指明在命名空间作用域定义显式特化的成员的语法 补充语法
CWG 727 C++98 类作用域不允许有全特化,但允许有部分特化 允许在任何作用域的全特化
CWG 730 C++98 不能全特化非模板类的成员模板 允许这些全特化
CWG 2478 C++20 不明确主模板的 constinitconsteval 是否会带入它的显式特化 不会带入
CWG 2604 C++11 不明确主模板的属性是否会带入它的显式特化 不会带入

参阅