成员模板

来自cppreference.com
< cpp‎ | language

模板声明(函数变量 (C++14 起))可以在任何不是局部类的 class、struct 或 union 的成员说明中出现。

#include <iostream>
#include <vector>
#include <algorithm>
 
struct Printer { // 泛型函子
    std::ostream& os;
    Printer(std::ostream& os) : os(os) {}
    template<typename T>
    void operator()(const T& obj) { os << obj << ' '; } // 成员模板
};
 
int main()
{
    std::vector<int> v = {1,2,3};
    std::for_each(v.begin(), v.end(), Printer(std::cout));
    std::string s = "abc";
    std::for_each(s.begin(), s.end(), Printer(std::cout));
}

输出:

1 2 3 a b c

在类作用域和外围命名空间作用域中都可以出现成员模板的部分特化。显式特化可以出现在主模板可出现的任何作用域中。

struct A {
    template<class T> struct B;         // 主成员模板
    template<class T> struct B<T*> { }; // OK:部分特化
//  template<> struct B<int*> { };      // 经由 CWG 727 OK:全特化
};
template<> struct A::B<int*> { };       // OK
template<class T> struct A::B<T&> { };  // OK

如果外围类定义也是类模板,那么在类体外定义成员模板时,它带有两组模板形参:一组是外围类的,另一组是其自身的:

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>(int)
    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; };
 
// OK:等价声明
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; } // 错误
};
(C++14 起)

成员变量模板

变量模板可以在类作用域内声明,此时它声明静态数据成员模板。细节见变量模板

(C++14 起)

缺陷报告

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

DR 应用于 出版时的行为 正确行为
CWG 1878 C++14 技术性允许 operator auto 禁止 operator auto