非类型模板参数
template<int t1>
void test(const int(&p)[t1]) //非类型模板参数
{
for(auto i = 0; i < t1; i++)
{
std::cout << p[i] << " ";
}
std::cout <<std::endl;
}
int main()
{
int p[10] = {
1,2,3,4,5,6};
test<10>(p);
return 0;
}
模板类成员函数
template<typename T>
class Test
{
public:
void test();
};
template<typename T>
void Test<T>::test() //指明模板类型参数Test<T>
{
std::cout << "1111111111111" << std::endl;
}
int main()
{
auto s = new Test<int>();
s->test();
}
定义在模板内内部的成员函数,被隐式定义为内联函数.
当使用模板类型时,必须提供模板实参,但是在类模板自己的作用域中,可以直接使用类型名,不需要实参.
template<typename T>
class Test
{
public:
Test& operator= (Test & t)//内联. 同时这里返回类型Test<T> 被简写Test,编译器自动推断
{
std::cout << "1111111111111" << std::endl;
return *this;
}
Test & operator++();
};
template<typename T>
Test<T> & Test<T>::operator++()//这里返回实例化的类型.必须给定模板实参
{
Test ret = *this;//这里函数体内,已经进入类的作用域,所以不必指定模板实参
...
}
类模板和友元
类模板含有一个非模板友元时,则友元被授权可以访问所有模板的实例.如果友元自身是模板,类可以授权所有友元模板实例,也可以只授权特定实例.
--类成员函数只有被调用时才会被实例化
template <typename> class B;
template<typename T>
bool operator==(const B<T> &, const B<T> &);
template <typename T>class B
{
//friend class A<T>;
friend bool operator==<T>(const B<T> &, const B<T> &);//这里operator==<T> 意味该友元函数是模板友元
T s;
void test(const B<T> &s)
{
std::cout << s << " " << s.s << std::endl;// 这个代码有个bug, 对象名和对象内成员名冲突,但是如果这个函数没有被调用,编译器不会实例化这个函数,也就不会检测这个错误
}
public:
B(T k)
{
s=k;
}
};
template<typename T>
bool operator==(const B<T>& a, const B<T> & b)//这里按照普通模板函数进行定义
{
std::cout << a.s << " " << b.s << std::endl;
return false;
}
int main()
{
B<int> s1(10);
B<int> s2(5);
s1==s2;
s1.test(s2);//调用这个函数,编译报错,注释这句话,则没有错误
return 0;
}
template<typename T> class Pal;
template<typename T> class C2;
class C
{
public:
C(int cc):c(cc){}
friend class Pal<C>;//这种需要前置声明
template <typename T> friend class Pal2;//pal2不需要前置声明,因为这句话声明足够清晰了
private:
int c;
};
template<typename T> class Pal
{
public:
Pal(T ii):i(ii)
{
}
int get()
{
return i.c;//访问C中的变量,因为Pal<c> 是C的友元
}
void getC2(C2<T>);
private:
T i;
};
template<typename T> class Pal2
{
public:
Pal2(T ii):i(ii)
{
}
int get(C x)
{
return x.c;//Pal2任意参数的实例化都是C的友元,故可以访问C的私有对象
}
template<typename X>void getC2(C2<X> &);//Pal2的任意实例都是C2的每个实例的友元
private:
T i;
};
template<typename T> class C2
{
friend class Pal<T>;
template<typename X> friend class Pal2;
friend class Pal3;
public:
C2(T aa):a(aa){}
private:
T a;
};
template<typename T>
void Pal<T>::getC2(C2<T> k)
{
std::cout << k.a << std::endl;//Pal<T>是C2<T>的友元,所以可以访问其私有变量
}
template<typename T>
template<typename X>//这里双层类型应该采用这种写法
void Pal2<T>::getC2(C2<X> &k)
{
std::cout << k.a << std::endl;//Pal<T>是C2<T>的友元,所以可以访问其私有变量
}
class Pal3
{
public:
Pal3(){}
void test(C2<int> s)
{
std::cout << s.a << std::endl;//Pal3 是 C2 任意参数的实例化对象的友元函数,所以可以访问其私有成员
}
};
int main()
{
C c(66);
Pal<C> pc(c);
std::cout << pc.get() << std::endl;
C2<int> c2(999);
Pal3 p3;
p3.test(c2);
Pal<int> pi(55);
C2<int> ci2(56);
pi.getC2(ci2);
Pal2<int> pc2(5);
std::cout << pc2.get(c) << std::endl;
Pal2<double> pc3(5.5);
pc2.getC2(c2);
pc3.getC2(c2);
return 0;
}
模板类型别名
template<typename T> using twin = std::pair<T,T>;
int main()
{
twin<int> k = {
1,1};
}
模板中静态成员的定义方式
template<typename T>
class M
{
private:
static int a;
};
template<typename T>
int M<T>::a = 10;
默认模板实参
template<typename T, typename K = int>//默认模板实参
int test(T a, K b)
{
return a+b;
}
template<typename T = int>//类模板默认实参
class Number
{
public:
Number(T ii):i(i){}
private:
T i;
};
int main()
{
std::cout << test<int,double>(10,20) << std::endl;
std::cout << test<int>(10,20) << std::endl;
Number<double> n1(10.4);
Number<> n2(11);// 尖括号里面没有任何参数,视为使用默认实参
}
一个类(无论是不是模板类)的成员函数可以是模板函数,这样的成员被称为成员模板.
成员模板不能是虚函数.(!!!!)
template<typename T>
class Number
{
public:
Number(T ii):i(i){}
template<typename X> void test(X &);//成员模板
private:
T i;
};
template<typename T>
template<typename X>
void Number<T>::test(X & s)
{
std::cout << "this is a test" << std::endl;
}
int main()
{
Number<int> s(10);
double k=10.0;
s.test(k);
}
控制实例化
当两个或多个源文件使用了相同的模板并提供相同的模板函数时,每个文件就会有一个该模板的一个实例.在大系统中,在多个文件中实例化相同的模板开销很大.可以采用显示实例化来避免开销.
//test.h
#ifndef TEST_H
#define TEST_H
template<typename T>
class Test
{
public:
Test()
{
}
};
template class Test<int>;//定义,实例化
#endif // TEST_H
//I.h
#ifndef I_H
#define I_H
#include "Test.h"
extern template class Test<int>;//实例化出现在其他位置
class I
{
public:
I()
{
class Test<int> s;
s = {};
}
virtual ~I()
{
}
};
#endif // I_H
//main.cpp
extern template class Test<int>;//实例化出现在其他位置
int main()
{
I ii{};
Test<int> tt{};
}
标准库类型转换
#include<type_traits>
template<typename T>
auto fcn(T a) -> typename std::remove_reference<decltype(*a)>::type
{
return *a;
}
template<typename T>
auto fcn2(T a) -> typename std::remove_pointer<decltype(*a)>::type
{
return *a;
}
int main()
{
int *s = new int(10);
std::cout << fcn2(s) << std::endl;
std::vector<int> k = {
20};
std::cout << fcn<std::vector<int>::iterator>(k.begin()) << std::endl;
}
模板实参推断和引用
如果函数参数的类型是T &, 只能传递给其一个左值
如果函数参数的类型是const T &, 则可以传递给它一个对象(const 或者非const),一个临时对象或者一个字面值常量
template<typename T>
void fff1(T& k)
{
std::cout << k << std::endl;
}
template<typename T>
void fff2(const T& k)
{
std::cout << k << std::endl;
}
int main()
{
int k = 10;
fff1(k);
fff2(k);
// fff1(5); //不能将字面值常量绑定给一个左值
fff2(5);//能将字面值常量绑定给一个const T&
}
引用折叠和右值引用参数
通常不能将一个左值赋值给右值引用.
void f(int && s)
{
}
int main()
{
int i = 10;
f(i);//这里报错,因为i 是个左值
}
那么在模板类型函数中,有了两个个例外规则.
规则一: 当将一个左值传递给函数的右值引用参数,并且右值引用指向模板类型参数,编译器推断模板类型参数为实参的左值引用类型
template<typename T>//模板类型参数为T
void f(T &&)//函数f的参数为右值引用,且该右值引用指向模板类型参数T
{
}
int i = 10;
f(i);//此时将i传递给f做参数时,编译器认为模板类型参数为T = int &, 而不是T = int(!!!)
T = int & 时,f 的参数T &&就被转换为 int& &&.通常不能定义引用的引用.为了避免int& &&被看成左值引用的右值引用,由此产生了引用折叠.
规则二: c++语言对于引用折叠做出了规定,如果我们间接创建引用的引用,则这些引用会形成折叠.折叠的结果是,对于给定的类型X:
—- X& &,X& &&, X&& & 均被折叠成X &
—- X&& &&被折叠成X &&
简单来说就是,除了右值引用的右值引用被定义为右值引用外, 左值引用的右值引用,右值引用的左值引用, 左值引用的左值引用都被定义成左值引用
template<typename T>
void f(T && s)//f接受一个模板类型参数的右值引用.
//调用f(i)时,按规则一,左值i 被传递给函数的右值引用参数,此时编译器推断模板类型参数T = int &int &.
//调用f(10)时,按规则二,int && &&折叠成 int &&, 此时T=int
{
T k = s;
k = 5;
std::cout << s << " " << k << std::endl;
//f(i)的输出结果是5 5.因为k 的类型T=int &, k 是s 的左值引用,所以k 变了,s 也变了
//f(10)的输出结果是10 5.因为k 的类型T=int, 所以k 变了,s 不变
}
int main()
{
int i = 5;
f(i); //将i 传递给f
f(10);
}
template<typename T> void f(T&&);//绑定非const 的右值
template<typename T> void f(const T&);//绑定左值或者const 右值
右值引用可以自由修改所绑定的临时变量的值
int &&s = 10;
int m = 20;
s = 20;
!!!!!!!!右值引用变量本身是个左值
int&&x=0;
int&&i = x; //错误
右值引用赋值右值引用
int &&temp = 0;
int &&i = 1;
i = temp;
转发
模板函数在实例化调用过程中,如果模板类型参数不是引用的话,那么就是函数的参数就是按值传递。
template<typename T>
void testx(T s)
{
s = 20; // 将值修改成20
}
int main()
{
int p = 10;
testx(p);
std::cout << p << std::endl;//输出10
}
模板类型参数既可以是普通类型,也可以是可调用对象。
template<typename T>
void testx(T s)
{
s();
}
void f()
{
std::cout << "test" << std::endl;
}
int main()
{
typedef void(*T)();
T p = f;
testx(p);//模板类型参数T被实例化为void(*func)()
}
上面演示了可调用对象的无参版本,假如我们希望可调用对象接受参数,并且可调用的对象的参数是按引用传递的
template<typename T,typename K>
void testx(T s, K k)
{
s(k);
}
void f(int & k)
{
k = 10;//假设需要改变参数的值
}
int main()
{
typedef void(*T)(int &);
T p = f;
int m = 0;
testx(p,m);//模板类型参数T被实例化为void(*func)(int &)
std::cout << m << std::endl;//!!!输出0
}
这里调用模板后,m并没有按我们预期的发生变化,这是为什么呢?
事实上,依据之前说,我们模板参数类型K是按值传递的。
testx(p,m)在执行过程中,
模板被实例化成
testx(void(*func)(int &), int t)
func 的引用参数绑定到了t,而不是testx(p,m)中的m。从而不会按我们预期的改变值。
std::forward 完美的解决了这个问题
std::forward < Type > 会返回 Type&&. 结合引用折叠,左引用折叠后还是左引用,右引用折叠后成右引用.从而保留引用的原始类型
template<typename T, typename K>
void testx(T s, K &&k)
{
s(std::forward<K>(k));
}
void f(int & k)
{
k = 10;//假设需要改变参数的值
}
void g(int &&k)
{
k = 13;
}
int main()
{
int m = 10;
testx(f, m);//f接受一个左值作为参数
testx(g, 12);//g接受一个右值作为参数
}
函数重载匹配。如果同样匹配的模板和非模板函数,优先匹配非模板函数
template<typename T>
void test(T t)
{
std::cout << "test " << std::endl;
}
void test(int t)
{
std::cout << "test 2" << std::endl;
}
int main()
{
int m = 10;
test(m);//输出test2
}
可变参数模板
template<typename ...Args>
void foo(Args ... r)
{
std::cout << sizeof...(r) << std::endl;
}
template<typename T>
std::ostream &test(std::ostream &os, T t)
{
return os << t;
}
template<typename T,typename ...Args>
std::ostream& test(std::ostream &os, const T& t, const Args& ... r)//扩展Args
{
os << t << ",";
return test(os, r...);//扩展r
}
int main()
{
foo(10,2.5,"ss");
test(std::cout, 1, "2.5", "dssds");
}
还可以来个更复杂点的
std::string debug(std::string t)
{
return t;
}
template<typename T>
std::string debug(const T&t)
{
return debug(std::string(t));
}
template<typename T>
std::ostream &test(std::ostream &os, T t)
{
return os << t;
}
template<typename T,typename ...Args>
std::ostream& test(std::ostream &os, const T& t, const Args& ... r)
{
os << t << ",";
return test(os, debug(r)...);
}
template<typename ...Args>
std::ostream & testA(std::ostream &os, Args... r)
{
return test(os, debug(r)...);//调用test扩展,并对每个元素先进行debug转换
}
int main()
{
testA(std::cout, "2.5", "dssds");
}
转发和扩展结合起来后,我们就可以实现较为庞大的函数了
template<class ... Args>
void emplace(Args&& ... args)
{
test(std::forward<Args>(args...));//假设test函数需要保留emplace的参数原始类型.和上一段代码相比,其实就是用std::forward<Args> 替换掉了debug
}
模板特例化
模板的定义对特定类型不适用,所以需要特例化
template<typename T>
int test(const T& s)
{
return s.size();
}
template <>
int test(const char* const &s)
{
return 10;
}
int main()
{
std::string k = "111";
std::cout << test(k) << std::endl;
const char * s= "111";
std::cout << test(s) << std::endl;
}
当我们特例化一个函数模板时,必须为原模板中的每个模板参数提供实参.为了指明我们正在实例化一个模板,应该使用关键字template<>指明
类模板特例化
类似于函数模板特例化,唯一要求必须在原模板定义所在的命名空间
namespace AA
{
template<typename T>
class M
{
public:
M()
{
std::cout << "000000" << std::endl;
}
};
};
struct Data
{
};
namespace AA
{
template<>
class M<Data>
{
public:
M()
{
std::cout << "111111" << std::endl;
}
};
}
int main()
{
AA::M<int> t;
AA::M<Data> s;
}
部分特例化
namespace AA
{
template<typename T>
class M
{
public:
void test(T k)
{
std::cout << "orignal" << std::endl;
}
};
};
namespace AA
{
template<>//都是template <> 打头
void M<int>::test(int k)//针对M<int>的成员test做特殊处理
{
std::cout << "part special" << std::endl;
}
}
int main()
{
AA::M<int> t;
AA::M<double> r;
t.test(10);
r.test(10);
}