模板声明(
类模板
、
函数模板
,以及
变量模板
(C++14 起)
)可以出现在任何非
局部类
的类、结构体或联合体的
成员规范
内部。
成员模板的偏特化既可以出现在类作用域,也可以出现在外围命名空间作用域。显式特化可以出现在主模板允许出现的任何作用域。
struct A
{
template<class T> struct B; // 主成员模板
template<class T> struct B<T*> {}; // 正确:偏特化
// template<> struct B<int*> {}; // 通过CWG 727允许:全特化
};
template<> struct A::B<int*> {}; // 正确
template<class T> struct A::B<T&> {}; // 正确
如果外围类声明本身是类模板,当成员模板在类体外定义时,它需要两组模板参数:一组用于外围类,另一组用于自身:
template<typename T1>
struct string
{
// 成员模板函数
template<typename T2>
int compare(const T2&);
// 构造函数也可以是模板
template<typename T2>
string(const std::basic_string<T2>& s) { /*...*/ }
};
// string<T1>::compare<T2> 的类外定义
template<typename T1> // 用于外围类模板
template<typename T2> // 用于成员模板
int string<T1>::compare(const T2& s) { /* ... */ }
成员函数模板
析构函数
和
拷贝构造函数
不能是模板。如果声明了一个可能被实例化为拷贝构造函数类型签名的模板构造函数,则会改用
隐式声明的拷贝构造函数
。
成员函数模板不能是虚函数,且派生类中的成员函数模板不能覆盖基类中的虚成员函数。
class Base
{
virtual void f(int);
};
struct Derived : Base
{
// 此成员模板不会覆盖 Base::f
template<class T> void f(T);
// 非模板成员重写可以调用模板:
void f(int i) override
{
f<>(i);
}
};
可以同时声明一个非模板成员函数和一个同名的模板成员函数。当发生冲突时(即某个模板特化与非模板函数的签名完全匹配),对该名称和类型的使用将指向非模板成员,除非显式提供了模板实参列表。
template<typename T>
struct A
{
void f(int); // 非模板成员函数
template<typename T2>
void f(T2); // 成员模板函数
};
// 模板成员定义
template<typename T>
template<typename T2>
void A<T>::f(T2)
{
// 部分代码
}
int main()
{
A<char> ac;
ac.f('c'); // 调用模板函数 A<char>::f<char>(char)
ac.f(1); // 调用非模板函数 A<char>::f(int)
ac.f<>(1); // 调用模板函数 A<char>::f<int>(int)
}
成员函数模板的类外定义必须与类内声明
等价
(关于等价性的定义请参见
函数模板重载
),否则将被视为重载。
struct X
{
template<class T> T good(T n);
template<class T> T bad(T n);
};
template<class T> struct identity { using type = T; };
// 正确:等效声明
template<class V>
V X::good(V n) { return n; }
// 错误:与X内部的任何声明都不等效
template<class T>
T X::bad(typename identity<T>::type n) { return n; }
转换函数模板
用户定义的
转换函数
可以是模板。
struct A
{
template<typename T>
operator T*(); // 转换为任意类型的指针
};
// 类外定义
template<typename T>
A::operator T*() { return nullptr; }
// 针对 char* 的显式特化
template<>
A::operator char*() { return nullptr; }
// 显式实例化
template A::operator void*();
int main()
{
A a;
int* ip = a.operator int*(); // 显式调用 A::operator int*()
}
在
重载解析
期间,转换函数模板的特化不会通过
名称查找
被发现。相反,所有可见的转换函数模板都会被考虑,并且通过
模板实参推导
(对转换函数模板有特殊规则)生成的每个特化都会被使用,就像通过名称查找到的一样。
派生类中的 using 声明不能引用来自基类的模板转换函数特化。
|
用户定义的转换函数模板不能拥有推导的返回类型:
struct S
{
operator auto() const { return 10; } // OK
template<class T> operator auto() const { return 42; } // error
};
|
(since C++14)
|
成员变量模板
变量模板声明可以出现在类作用域中,此时它声明的是静态数据成员模板。详见
变量模板
。
|
(since C++14)
|
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
|
缺陷报告
|
适用标准
|
发布时行为
|
正确行为
|
|
CWG 1878
|
C++14
|
operator auto 在技术上被允许
|
operator auto 被禁止
|