重载模板以适用不同的情况
- 下面用于交换两个对象的函数模板exchange可以处理简单类型,但如果T是下面的类,就没必要再拷贝一次对象并调用两次赋值运算符,而只需要使用成员模板exchangeWith交换内部的指针
template<typename T>
class Array {
public:
Array(const Array<T>&);
Array<T>& operator=(const Array<T>&);
void exchangeWith(Array<T>* b)
{
T* tmp = data;
data = b->data;
b->data = tmp;
}
T& operator[](std::size_t k)
{
return data[k];
}
private:
T* data;
};
template<typename T> inline
void exchange(T* a, T* b)
{
T tmp(*a);
*a = *b;
*b = tmp;
}
- 但使用成员模板的用户要记忆并在适当情况下使用这个接口,为了免去这个负担,重载函数模板即可实现一致性
template<typename T> inline
void quick_exchange(T* a, T* b)
{
T tmp(*a);
*a = *b;
*b = tmp;
}
template<typename T> inline
void quick_exchange(Array<T>* a, Array<T>* b)
{
a->exchange_with(b);
}
void demo(Array<int>* p1, Array<int>* p2)
{
int x = 42, y = -7;
quick_exchange(&x, &y);
quick_exchange(p1, p2);
}
- 虽然上述两种交换的算法都能交换指针指向的值,但各自的副作用截然不同
struct S {
int x;
} s1, s2;
void distinguish(Array<int> a1, Array<int> a2)
{
a1[0] = 1;
a2[0] = 2;
s1.x = 3;
s2.x = 4;
int* p = &a1[0];
int* q = &s1.x;
quick_exchange(&a1, &a2);
quick_exchange(&s1, &s2);
}
int main()
{
int* a = new int[3];
a[0] = 1;
int* p = &a[0];
int* b = new int[3];
b[0] = 2;
int* q = b;
b = a;
a = q;
std::cout << a[0] << b[0] << *p;
}
int main()
{
int i = 1, j = 2;
int* p = &i;
int* q = p;
p = &j;
std::cout << *q;
}
- 如果不使用成员模板exchangeWith,可以将函数改为递归调用(因此不再使用inline)
template<typename T>
void quick_exchange(T* a, T* b)
{
T tmp(*a);
*a = *b;
*b = tmp;
}
template<typename T>
void quick_exchange(Array<T>* a, Array<T>* b)
{
T* p = &(*a)[0];
T* q = &(*b)[0];
for (std::size_t k = a->size(); k-- != 0; )
{
quick_exchange(p++, q++);
}
}
签名(Signature)
- 只要具有不同的签名,两个函数就能同时存在于同一个程序中,函数签名(简单理解就是函数声明中所包含的信息)定义如下
- 函数的非受限名称
- 函数名称所属的作用域
- 函数的cv限定符
- 函数的&或&&限定符
- 函数参数类型
- 函数模板还包括返回类型、模板参数和模板实参
- 以下模板具有不同的签名,其实例化体可以在一个程序中同时存在
template<typename T1, typename T2>
void f(T1, T2);
template<typename T1, typename T2>
void f(T2, T1);
template<typename T>
long g(T);
template<typename T>
char g(T);
#include <iostream>
template<typename T1, typename T2>
void f(T1, T2) {}
template<typename T1, typename T2>
void f(T2, T1) {}
int main()
{
f<char, char>('a', 'b');
}
- 只有两个模板出现在不同的编译单元,两个实例化体才能在一个程序中同时存在
#include <iostream>
template<typename T1, typename T2>
void f(T1, T2)
{
std::cout << 1;
}
void g()
{
f<char, char>('a', 'b');
}
#include <iostream>
template<typename T1, typename T2>
void f(T2, T1)
{
std::cout << 2;
}
extern void g();
int main()
{
f<char, char>('a', 'b');
g();
}
函数模板的偏序(Partial Ordering)规则
- 多个函数模板匹配实参列表时,C++规定了偏序规则来决定调用哪个模板,之所以叫偏序是因为一些模板可以是一样特殊的,此时则会出现二义性调用
#include <iostream>
template<typename T>
void f(T)
{
std::cout << 1;
}
template<typename T>
void f(T*)
{
std::cout << 2;
}
int main()
{
f<int*>((int*)nullptr);
f<int>((int*)nullptr);
f(0);
f(nullptr);
f((int*)nullptr);
}
- 决定哪个模板更特殊的规则为:考虑两个模板T1和T2,不考虑省略号参数和默认实参,用假想的类型X替换T1,看T2是否能推断T1的参数列表(忽略隐式转换),再把T1和T2反过来做一次这个过程,能被推断的那个就是更特殊的。由这个规则推出的一些结论:特化比泛型特殊、
T*
比T特殊、const T
比T特殊、const T*
比T*
特殊
template<typename T>
void f(T*, const T* = nullptr, ...);
template<typename T>
void f(const T*, T*, T* = nullptr);
void example(int* p)
{
f(p, p);
}
普通函数会被重载优先考虑
- 函数模板也可以和非模板的普通函数重载,其他条件相同时优先调用非模板函数
template<typename T>
void f(T)
{
std::cout << 1;
}
void f(int&)
{
std::cout << 2;
}
int main()
{
int x = 0;
f(x);
}
template<typename T>
void f(T)
{
std::cout << 1;
}
void f(const int&)
{
std::cout << 2;
}
int main()
{
int x = 0;
f(x);
const int y = 0;
f(y);
}
class C {
public:
C() = default;
C(const C&) { std::cout << 1; }
C(C&&) { std::cout << 2; }
template<typename T>
C(T&&) { std::cout << 3; }
};
int main()
{
C x;
C x2{x};
C x3{std::move(x)};
const C c;
C x4{c};
C x5{std::move(c)};
}
可变参数函数模板的重载
#include <iostream>
template<typename T>
void f(T*)
{
std::cout << 1;
}
template<typename... Ts>
void f(Ts...)
{
std::cout << 2;
}
template<typename... Ts>
void f(Ts*...)
{
std::cout << 3;
}
int main()
{
f(0, 0.0);
f((int*)nullptr, (double*)nullptr);
f((int*)nullptr);
}
#include <iostream>
template<typename... Ts>
class Tuple {};
template<typename T>
void f(Tuple<T*>)
{
std::cout << 1;
}
template<typename... Ts>
void f(Tuple<Ts...>)
{
std::cout << 2;
}
template<typename... Ts>
void f(Tuple<Ts*...>)
{
std::cout << 3;
}
int main()
{
f(Tuple<int, double>());
f(Tuple<int*, double*>());
f(Tuple<int*>());
}
函数模板全特化
- 函数模板特化引入了重载和实参推断,如果能推断特化版本,就可以不显式声明模板实参
template<typename T>
int f(T)
{
return 1;
}
template<typename T>
int f(T*)
{
return 2;
}
template<>
int f(int)
{
return 3;
}
template<>
int f(int*)
{
return 4;
}
- 函数模板特化不能有默认实参,但会使用要被特化的模板的默认实参
template<typename T>
int f(T, T x = 42)
{
return x;
}
template<>
int f(int, int = 35)
{
return 0;
}
template<typename T>
int g(T, T x = 42)
{
return x;
}
template<>
int g(int, int y)
{
return y/2;
}
int main()
{
std::cout << g(0) << std::endl;
}
- 特化声明的不是一个模板,非内联的函数模板特化在同个程序中的定义只能出现一次,通常应该把特化的实现写在源文件中。如果想定义在头文件内,可以把特化声明为内联函数
#ifndef TEMPLATE_G_HPP
#define TEMPLATE_G_HPP
template<typename T>
int g(T, T x = 42)
{
return x;
}
template<>
int g(int, int y);
#endif
#include "template_g.hpp"
template<>
int g(int, int y)
{
return y/2;
}
类模板全特化
- 特化的实参列表必须对应模板参数,非类型值不能替换类型参数,如果有默认实参可以不指定对应参数
template<typename T>
class Types {
public:
using I = int;
};
template<typename T, typename U = typename Types<T>::I>
class X;
template<>
class X<void> {
public:
void f();
};
template<> class X<char, char>;
template<> class X<char, 0>;
int main()
{
X<int>* pi;
X<int> e1;
X<void>* pv;
X<void, int> sv;
X<void, char> e2;
X<char, char> e3;
}
template<>
class X<char, char> {}
- 类特化和泛型模板的唯一区别是,特化不能单独出现,必须有一个模板的前置声明。特化声明不是模板声明,所以类外定义成员时应该使用普通的定义语法(即不指定
template<>
前缀)
template<typename T>
class X;
template<>
class X<char**> {
public:
void print() const;
};
void X<char**>::print() const
{
std::cout << "pointer to pointer to char\n";
}
template<typename T>
class A {
public:
template<typename U>
class B {};
};
template<>
class A<void> {
template<typename U>
class B {
private:
static int i;
};
};
template<typename U>
int A<void>::B<U>::i = 1;
- 可以用全特化代替对应的某个实例化体,但两者不能在一个程序中同时存在,否则会发生编译期错误
template<typename T>
class X {};
X<double> x;
template<>
class X<double>;
- 如果在不同的编译单元出现这种情况很难捕捉错误,如果没有特殊目的应该避免让模板特化来源于外部资源。下面是一个无效的例子
template<typename T>
class X {
public:
enum { max = 10; };
};
char buffer[X<void>::max];
extern void f(char*);
int main()
{
f(buffer);
}
template<typename T>
class X;
template<>
class X<void> {
public:
enum { max = 100; };
};
void f(const char* buf)
{
for (int i = 0; i < X<void>::max; ++i)
{
buf[i] = '\0';
}
}
成员全特化
template<typename T>
class A {
public:
template<typename U>
class B {
private:
static int x;
};
static int y;
void print() const
{
std::cout << "generic";
}
};
template<typename T>
int A<T>::y = 6;
template<typename T>
template<typename U>
int A<T>::B<U>::x = 7;
template<>
class A<bool> {
public:
template<typename U>
class B {
private:
static int x;
};
void print() const {}
};
template<>
class A<bool>::B<wchar_t> {
public:
enum { x = 2 };
};
template<>
int A<void>::y = 12;
template<>
void A<void>::print() const
{
std::cout << "A<void>";
}
template<>
template<typename U>
class A<wchar_t>::B {
public:
static long x;
};
template<>
template<typename X>
long A<wchar_t>::B<X>::x;
template<>
template<>
class A<char>::B<wchar_t> {
public:
enum { x = 1 };
};
- 普通类的成员函数和静态成员变量在类外的非定义声明是非法的
class A {
void f();
};
void A::f();
template<typename T>
class A {
public:
template<typename U>
class B {
private:
static int x;
};
static int y;
void print() const
{
std::cout << "generic";
}
};
template<>
int A<void>::y;
template<>
void A<void>::print() const;
- 如果静态成员变量只能用默认构造函数初始化,就不能定义它的特化
class X {
public:
X() = default;
X(const X&) = delete;
};
template<typename T>
class Y {
private:
static T i;
};
template<>
X Y<X>::i;
template<>
X Y<X>::i{};
template<>
X Y<X>::i = X();
类模板偏特化
template<typename T>
class X {};
template<typename T>
class X<const T> {};
template<typename T>
class X<T*> {};
template<typename T, int N>
class X<T[N]> {};
template<typename A>
class X<void* A::*> {};
template<typename T, typename A>
class X<T* A::*> {};
template<int I, int N>
class S {};
template<int N>
class S<2, N> {};
template<typename... Ts>
class Tuple {};
template<typename T>
class Tuple<T> {};
template<typename T1, typename T2, typename... Ts>
class Tuple<T1, T2, Ts...> {};
- 偏特化可能产生无限递归,解决方法是在偏特化前提供一个全特化,匹配时全特化会优于偏特化
template<typename T>
class X {};
template<>
class X<void*> {};
template<typename T>
class X<T*> {
public:
X<void*> x;
};
template<typename T, int I = 3>
class X {};
template<typename T>
class X<int, T> {};
template<typename T = int>
class X<T, 10> {};
template<int I>
class X<int, I*2> {};
template<typename U, int K>
class X<U, K> {};
template<typename... Ts>
class Tuple {};
template<typename T, typename... Ts>
class Tuple<Ts..., T> {};
template<typename T, typename... Ts>
class Tuple<Tuple<Ts...>, T> {};
- 偏特化会匹配更特殊的版本,如果匹配程度一样就会出现二义性错误
template<typename T>
class X {};
template<typename T>
class X<const T> {};
template<typename T, int N>
class X<T[N]> {};
X<const int[3]> x;
template<typename T>
class X {};
template<typename T>
class X<T*> {
public:
void f();
};
template<typename T>
void X<T*>::f() {}
变量模板全特化
template<typename T>
constexpr std::size_t X = sizeof(T);
template<>
constexpr std::size_t X<void> = 0;
template<typename T>
typename T::iterator null_iterator;
template<>
int* null_iterator<std::vector<int>> = nullptr;
auto p = null_iterator<std::vector<int>>;
auto q = null_iterator<std::deque<int>>;
变量模板偏特化
template<typename T>
constexpr std::size_t X = sizeof(T);
template<typename T>
constexpr std::size_t X<T&> = sizeof(void*);
- 和变量模板的全特化一样,偏特化的类型不需要和原始模板匹配
template<typename T>
typename T::iterator null_iterator;
template<typename T, std::size_t N>
T* null_iterator<T[N]> = nullptr;
auto p = null_iterator<int[3]>;
auto q = null_iterator<std::deque<int>>;