template< class _T>
void F(){
return;
}
此时, F()
是错误的, 必须要有模板参数;
template< class _T = void>
void F(){
return;
}
= void
就是默认值, 此时F()
也可以了.
不要忘记, 函数只有 (全特化)
template <class T> void func( T t){}
template <> void func< int>( int t){}
template <> void func< int>( int t){}
你写成:
都是错的!!! 这里非常非常重要!!! 仔细理解
因为, 主模板函数 只有一个T
, 你在全特化时
, 最终x 和 y
都会变成同一个T
, 所以, x
和 y
, 必须严格相同
template <class T> void func( T t){} ' 111 '
template <class T> void func( ST<T> t){} ' 222 '
template <> void func< int>( int t){} ' 333 '
template <> void func< int>( ST< int> t){} ' 444 '
首先, 1 和 2, 都是主模板函数, 1 和 2, 构成: 函数重载
一定要注意 函数重载
这个概念!! 函数重载是一个很大的概念!
比如, 你有n个函数重载, 每一个重载函数, 都可以拓展出 一系列的(全特化)… n个函数重载, 就意味着: 有n个 不同的 全新的 函数!!!
3是1的全特化, 4是2的全特化; 但3和4, 不是全新的函数, 他们归根结底 还是1和2的类型!!
拓展知识:
我们知道, std里面 不允许添加新的东西!!!
换句话说: 可以全特化std的函数, 但不可以重载std的函数!!! 因为, 重载 意味着 添加新东西了
namespace std{
template <class T> void swap( T &, T &); ' 这是std的官方函数 '
}
如果你新加了:
namespace std{
template <class T> void swap( ST< T> & a, ST< T> & b); ' 语法上可行 '
}
但是, 这个模板函数, 与原本的std::swap
是 重载关系!! 不是特化关系!! 这违背了: std的不可以添加 新的东西!!!
全特化: 只要有template<>
就是全特化
这里的函数, 包括: 全局函数, 类的成员函数
template <class T> void func(){}
template <> void func< int>(){} ' 全特化 '
template <class T> class STT{};
template <> class STT<int>{};
偏特化: 只有类模板, 才允许偏特化!!!
模板类的模板函数, 也不可以偏特化!!!只要是函数, 不管什么函数, 都不允许偏特化
template <class T, class TT> class ST{};
template <class T> class ST<T, int>{}; ' 个数上的 偏特化 '
template <class T, class TT> class ST<T &, TT>{}; ' 类型(const/ &/ */ volatile)上的 偏特化'
template <class T, class TT> class ST< Obj1<T> , Obj2<TT> >{};
' 请注意这种写法!!! 也属于(类型上的) 偏特化 '
A.h
template <class T> extern void Func( T t); ' 模板函数 - primary '
template <class T> extern void Func( O< T>); ' 模板函数 - partial '
template <class T> class O{ ' 模板类 '
public:
O();
void func();
template <class TT> void ffunc( TT t);
};
A.cpp
template <class T> void Func( T t){ ...}
template <class T> void Func( O< T>){ ...}
template <class T> O< T>::O(){ ...}
template <class T> void O< T>::func(){ ...}
template <class T> template <class TT> void O< T>::ffunc( TT t){ ...}
对于普通函数, 这样写肯定是正确的; 但是, 对于模板函数, 这样是报错的!!!
你必须把A.cpp里的 所有代码, 写到 A.h里;
所谓, 写到A.h里, 有两种实现方式
template <class T> extern void Func( T t);
template <class T> void Func( T t){ ...}
即, A.h 原封不动, 直接放到他下面即可template <class T> void Func( T t){ ...}
两种写法都是完全等价的; 方式一看起来更简洁些.
为什么呢?
对于 非模板的函数, 他有两个步骤: (声明extern) 和 (定义/实现)
但是, 对于模板函数, 他有三个步骤: (声明) 和 (定义) 和 (实现)
你的template< T> Func( T t){ ... }
, 其实是定义, 不是(实现)!!!
这个 (实现), 又分为两种
对于第2种的 这个(实现), 有两点要注意:
1: 他是编译器 在 编译 生成.o文件 时, 自动帮我生成的
2: 你必须要有 (定义), 否则, 编译器也生成不了; 只有(声明) 不行!!
从 定义带有template
到 实现将template变成具体的type (这个type取决于你的code)
, 这个过程, 称为 (实例化);
实例化, 发生在 (编译)阶段 <即生成单独的.obj文件>时, 而不是 (链接)阶段.
这句话的意思, 等价于: .obj
文件里, 是没有template函数的!!! template都已经被 (实例化)成 具体类型了!!!
这句话, 非常重要!!! 下面会解释它
比如:
extern void Func( T t);
void Func( T t){ ... }
#include "A.h"
Funt( int);
当我们在 main.cpp
里, 调用: Funt( int);
这个函数, 我们并没有实现, 所以编译器会给我们实现; 我们知道, (实现)一个模板函数, 必须要得到他的 (定义)
而, main.obj
这个文件, 他只有A.h
即(声明), 没有定义!!! 所以, 编译器会报错!!!
A.cpp
编译后的 A.obj
文件, 会是 (NULL) 里面内容是空的!!!
一定要仔细理解: (模板函数的) 声明 和 定义, 必须是在 (同一个文件)里.
你在 A.cpp
, 他在预处理后, 声明和定义, 不就是在 (同一个文件)里啊; 为什么会出错呢??
因为, 对于main.cpp
, 他在预处理后 , 他的声明和定义 不在同一个文件里.
总之: 你的code 在使用 模板时, 最终必须找到 (具体的 实现代码);
template
叫做占位; 即可以忽略
template<typename A, typename B> // 这个类, 不管什么(全特化, 偏特化), 一定是: Is, ?>的写法;
struct Is // 这里不写, ?>, 则说明 这个类是主类; (primary template)主的
{ };
template<> // 空的<> 则说明是 (full specialize)
struct Is<int, int> // 不管是(full 还是 partial), 这里都是2个, ?>
{ }; // full specialize
template <typename A> // 这里虽然只有一个, 但并不代表 他对应的是: Is>!!! IS永远是, ?>的写法; 这里的, 针对的是: , ?>里的!!
struct Is<A, int>
{ }; // partial specialize
template <typename A> // 只要这里是<> 则是full; < 小于2个> 则是partial
struct Is<A, A>
{ }; // partial specialize
用这个trick, 可以用来判断, 两个type是否一样;
is_same<T, const T> <T, T &> <T, volatile T> 都是false
is_same<int, const int>::value == false
模板函数, 他的作用域 是本文件 (即和static一样)
比如我们要写一个全局的 模板函数
' global.h里: '
template <typename _T>
void Func( _T _t){
cout << _t;
}
他不像 全局函数, 得使用extern
, 你直接在.h
里 给他实现, 因为模板函数的 作用域 是 当前cpp文件
void FF(){}
template <typename _F, typename ... _S>
void FF( _F _f, _S... _s){
static int count = 0;
++ count;
cout << count;
FF( _s ...);
}
不管是: FF( 123, "abc", true);
还是 FF( 123, 123, 123, 123)
,
输出都是: 1 1 1 1 1 1
即每一次的count, 都是1
FF(123, "abc", true); FF(123, "abc", true);
输出是: (1 1 1) (2 2 2)
比如, 你第一次: FF(123, "abc", true);
时
产生了3个 实例函数:
FF
是一个实例函数!!! 而且, 是一个static的 实例函数
FF
是一个实例函数!!! 而且, 是一个static的 实例函数
FF< bool>
是一个实例函数!!! 而且, 是一个static的 实例函数
比如此时, 你调用: FF( false);
, 即他会进入: FF
实例函数. 这个函数, 已经实例化了!!!
所以, 这个实例化的函数, 调用了2次!!!! 一次是:FF(123, "abc", true)时, 一次是: FF(false)时
故, 此时的FF( false)
, 输出的count是: 2
template< typename T >
T add(T a, T b){
return a + b;
}
' 函数模板的调用,分为2种方式: '
1,不写模板参数,通过“实参”来推导模板参数
add(3, 4); 他会自动的推断出,T = int
但add(3, 4.0f); 就会报错
2,提供模板参数
add<int>(3, 4);
在成功推断出T的类型后,就会进行“实例化”操作: 即然后一份“代码”
这份代码 和 你的模板函数非常相像,只是把T变成了int而已!
' 模板参数里,出了放类型, 还可以放“值”(但值必须是常量,只能是int、LL) '
template< int param >
int func(){
return param;
}
func(); 报错
func<32>(); 正确
int A = 32;
func< A >(); 报错, 必须把A写成const、constexpr
我们知道,“ 头文件里,是不可以实现的,只能写定义!! ”
函数: 只能写声明,不能实现!!!
void func(){ ... } 错误!!!
必须是: void func(); 然后在某个cpp里,实现它
' 而 模板函数则不同, 模板函数 必须要写实现!!! '
即,头文件里:
template <typename T>
void func(T a){ ... 实现出来!! }
可以参考,下面的 类模板的 特化。
' 貌似,“函数的 偏特化” 是not allowed,而类模板 是可以 偏特化的 '
template <typename T, typename U>
void func(){ } ' 泛化1 '
template <>
void func <int, int> (){ } ' 泛化1的特化 '
// template 错误!! 函数模板 不允许 偏特化
// void func(){} 类模板,是可以这样写的
template <typename T>
void func(T t){} ' 重点!! 这里是“函数 重载”!!! 而且是: 泛化2'
template <>
void func<int>(int){} ' 泛化2 的 特化 '
--------------------------------------------
template <typename T>
void func(){ }
/ 调用: func() 是会报错的!!!
/ 因为,func() 会变成: func<>(), 即0个模板参数
/ 而我们写的函数,接收的是: 1个 模板参数
' 即对于模板函数, 他有 2层限制!! 模板参数数量 和 函数参数数量,都要匹配!! '
--------------------------------------------
一个同名的函数func
他可以有:
1, 普通重载函数: (参数类型、参数数量)
void func(){} ' 注意,函数重载 是不包含 返回值的!! '
void func(int){} ' 否则,用户调用func(123),怎么指定返回值 .. '
void func(char){}
' 你调用func( xx ): 他需要看,xx的数量 xx的类型 '
' 来和上面的 函数 进行匹配 '
2, 泛化重载函数: (模板参数数量、函数参数个数、函数参数类型)
template<typename T> void func(T){ 11 }
template<typename T, typename U> void func(T, U){ 22 }
template<typename T> void func(int,int){ 33 }
' 你调用 func( xx ): 他根据xx 的 数目 和 类型,先匹配 “函数参数” '
/ 比如你调用: func(1, 2) {1,2都是int},符合的函数有: 22 和 33
/ 然后, 再“推演出 模板参数”
/ 比如他尝试去匹配33: 因为(33函数参数里,没有模板参数!! 这是2个int,类型是确定的)
/ 即 没有推演出 模板参数!! 然而,33函数 是需要1个 “模板参数”的
/ 你选择没有模板参数,所以不匹配。
/ 再去尝试匹配22: 因为(22函数参数是:T和U,两个模板参数)
/ 因为,你的func(1,2),根据1和2的值,推演出是:(int, int)
/ 即 T= int, U = int, “即,2个模板参数”,符合22函数
/ 因此, func(1, 2) 会调用 22函数
' 即,先对“函数参数 的 数目、类型”,进行匹配!!! (得到22 和 33) '
' 再对“模板参数 的 数目” 进行匹配(注意此时没有类型, 毕竟模板参数T 本来就没有类型, 你任意类型 都可以对应T) '
3, 泛化 的 特化函数(上面的11/22/33,都可以有对应 若干 特化!! '当然,必须是全特化')
特化必须满足: (函数参数 类型+数目 相同)且(模板个数 相同)
template<typename T> void func(int){ } ' 这是泛化11 '
template<> void func<int>(int){} ' 这个11的特化 '
template<> void func<char>(int){} ' 这个11的特化 '
/ template<> void func<char>(char){} 错!! 函数参数不同
/ template<> void func<int,int>(int){} 错!! 模板参数不同!!
/ 即11 的 特化,必须: 函数参数是(int),模板参数个数是1个
/ 即: template<> void func<写1个具体类型>(int){}
template<typename T, typename U> void func(T,int){ }' 这是泛化22 '
template<> void func<char,char>(char,int){} ' 22的特化 '
/ template<> void func<int,int>(int,char){} 错(函数第二参数必须是int)
/ template<> void func<float,bool>(double,int){} 错!!
/ 因为,float与double 类型不同!! 这你要对应“泛化22”这个函数!!
/ 可以看到: 模板第一参数T 和 函数第一参数,都是T
/ 所以,你在写特化时,也要相同!!!
/ 即: template<> void func<类型1, 类型2>(类型1, int){}
' 只有形如这个样子,才是 22的特化!!! '
/ 思考: 为什么func(1,2) 会报错??
/ 不用看“泛化22”的特化,(如果泛化都匹配不成功,特化更不成功!!)
/ func(1, 2): 先进行(函数参数 匹配),与“泛化22” 匹配成功
/ 再进行(模板参数个数 匹配),得到T=int
/ 但根本得不到U这个模板参数(所以,func(1,2)得到1个模板参数,而不是2个) 所以匹配不成功
/ 但是, func<int,int>(1,2) 就可以成功
' 上面的这些: 1, 普通重载函数、 2, 泛化重载函数、 3, 泛化 的 特化函数 '
' 都是可以同时存在的!!! '
----------------------------------------------------------------------------
' 比如:用户调用的函数Fun, 如果进入了 “泛化11” 的 “特化11_x”中去 成功匹配'
' 那么, 这个“泛化11”函数 肯定也一定可以和 这个用户的Fun,进行匹配!!! '
这是重点!!!
什么叫“特化”, 他是在“泛化”的基础上,进行的特化
都能和“特化”进行匹配了,那肯定更可以和“泛化”进行匹配
1, 如果你在写“泛化函数”,
template <> void func...
写成这样,一定报错!!!!
/ 什么叫“泛化”?? 即“模板编程”,那么你的template <>里面 怎么可以是空的???
/ 至少是: template <typename T>,这才叫做是: “泛化函数”
2, 如果你在写“泛化函数”,
template <typename T, ..>
void func<>(){}
写成这样,一定报错!!!!
/ 对于“泛化函数”,你的func后面(参数列表前面,不能有<>)
/ 你写成: func<>(){},只要带“尖括号”,这就是在写“特化”!!!
即,“泛化函数” 一定形如:
template <typename T, ..>
void func( ... ){}
3, “特化函数”
单写一个: template <> void func<int>(){ }
一定报错!!!
' 模板参数为空 + 函数名后带<>, 这就是“特化” '
/ 你写“特化函数”时,必须要先写好他的 “泛化”!!!
/ 有了“泛化”函数,作前提,才能写他的 各种特化!!!
即,特化一定形如: '函数的特化,即全特化 '
template <>
void func< t.. >( p.. ){} ' 且t.. 和 p.., 必须和其泛化函数 对应 '
' t.. 的个数 = 其泛化函数的 模板参数个数 '
' p.. 的个数 + p..的类型 = 其泛化函数的 函数参数 '
4, 空模板
template <> ' 说明,他一定是: 特化函数 '
void func(){} ' 错!! 特化函数,必须是形如:void func< .. >() '
template<>
void func<>(){}
错!! 虽然他是“特化”,但 func<> 告诉我们: 其“泛化函数” 是 “空的 模板参数”
即,其泛化函数为: template <> ....
' 我们又知道: 只要是“泛化函数”,则 模板参数 必须 >= 1个 '
因此, 这种写法 是错的!!!
' 你这么写,其实就写一个: void func(){} 即可!!! '
' 可能你不知道: (重载函数)(泛化)(特化) 都可以 并存 的!!! '
template< typename T >
class ST{
T data;
void print(){ DE(data); ED; }
T get();
};
template< typename T >
T ST<T>::get(){ ' 声明和实现,都必须在同一个.h里!! '
return this->data;
}
ST<int> t;
t.data = 123;
1, ' 类模板必须提供模板参数,没有实参推演!!!! '
即: ST t; 报错!
2, 类模板的类,声明和实现,必须写在同一个.h文件里
不可以像普通类一样,不可分开。 原因是: 实例化类的缘故
3, 一旦是类模板的类, 则永远不可以 脱离模板!!
即ST t; 是错的!!!
比如: ST和STT都是模板类,则STT< ST > stt; 是错的!!!
必须是: STT< ST<?> > stt;
template <typename T>
class ST{ }
template <typename T>
void func( ST< T> ){ }
ST<int> st;
func<int>( st ); / 因为函数可以推导,写成 func(st)也可以。
ST<char> st;
func<int>(st); ' 这肯定报错的! '
template <typename _T>
class ST{
template <typename TT>
void func(TT){} ' 模板 成员函数 '
/ 这个函数,与ST是否是模板类 无关!!!
/ 通过obj.func<int>(12) 或 obj.func(12) 来调用
/ 模板成员函数,属于函数,可以实参推导
}
我们写的一个“泛化”的版本,它可以接收 “所有、任意”的类型T
而比如,我们想 对于T = 某些特定类型时,单独的处理他
此时,就有了“特化” ' 前提是,必须要先有泛化'
template < typename T1, typename T2 >
class ST{ ' 泛化 '
};
template < >
class ST <int, int> { ' 全特化1 '
};
template < >
class ST <char, char> { ' 全特化2 '
};
' 所谓全特化: 对于泛化中的“每一个T”,你都需要特定他的具体类型 '
ST<char, int> t1; // 进入 泛化版本
ST<int, int> t2; // 进入 特化1版本
ST<char, char> t3; // 进入 特化2版本
' 所谓偏特化: 对于泛化中的“所有T”,你可以特定某些T的具体类型 '
template < typename T1, typename T2 >
class ST{ ' 泛化 '
};
template < typename T >
class ST < int, T > { ' 偏特化 '
}; // 所有: 第一个参数T1==int的,都会进入这里。
ST< 非int, 任意 > t; // 泛化
ST< int, 任意 > t1; // 偏特化
template < typename T >
class ST{ // 此时,这个T 是可以接受 任何类型的!!!
}; // ...
// 都会进入这里!!
但是,一定要注意: T*、T&、T&&、const T、const T&、、、、
他们,都不是“真正意义上的类型”!!!
T是类型, 这些“指针*, 引用&, 常量const,,,” 都是'修饰',不算是“真正的类型!!”
' 因此,一个T 包含了(int、int*、int&、int&&、const int、、、) '
' 针对这一点,也可以做“特化”, 称为“类型范围上的 特化” '
' 定义xx为: 所有“基本类型 int/char/...” 和 “自定义的类名” '
' 不带有任何的修饰(* & const 等) '
template < typename T >
class ST{}; ' < xx > 进入这里 '
template < typename T >
class ST < T * >{}; ' < xx * > 进入这里,xx上面有定义 '
template < typename T >
class ST < T & >{}; ' < xx * > 进入这里,xx上面有定义 '
template < typename T >
class ST < T && > {};' < xx && > 进入这里,xx上面有定义 '
template < typename T >
class ST < const T >{};' < const xx > 进入这里,xx上面有定义 '
template < typename T >
class ST < const T & >{};' < const xx & > 进入这里,xx上面有定义 '
我们知道,“ 头文件里,是不可以实现的,只能写定义!! ”
函数: 只能写声明,不能实现!!!
void func(){ ... } 错误!!!
必须是: void func(); 然后在某个cpp里,实现它
类: 可以写成员函数的实现 (因为,类的作用域 是当前cpp)
而 模板函数则不同, 模板函数 必须要写实现!!!
即,头文件里:
template <typename T>
void func(T a){ ... 实现出来!! }
' 即,无论函数模板 类模板, 不要写声明,直接声明实现写到一块,而且放到“.h头文件”里 '
1, 函数模板有实参推演,类模板没有!!!
func<int>(123) 可以写成: func(123); 'func是函数'
ST<int>() 不可以写成: ST(); ' ST()是构造函数 '
' 因为,ST是类模板,没有实参推演,你必须手动写上去 '
不管是 模板类,还是 模板函数,只要当“使用”时(即编译),就会进行 “实例化”
即,产生一份 T=具体类型 的代码,(并不发生在 预处理阶段)
template <typename T>
void func(T){}
但你写了: func<int>(123)时,此时,就会产生一份
void func(int){}的代码!!
思考一个问题:
com.h里:
template <typename T>
void func(T){}
1.cpp 和 2.cpp里,都有: func<int>(123)这句话!!
'由于每个cpp,都是“独立编译”的'
因此,在1.cpp里 和 2.cpp里,都会产生 完全相同的 实例化代码
' 造成了额外开销,增加了编译时间 '
处理:
在某一个.cpp里:
template void func<int>(int);
在其他cpp里:
extern template void func<int>(int);
注意,我们讲的是: “可变长 模板参数”,不是 “可变长 函数参数” '可变长函数参数在另外文章有讲 '
可变长模板,在C++11引入(Variadic template)
// 注意这个T,他并不是一个类型!! 而是多个类型,即T = {char, int, ... }
template <typename... T>
void func(T... params){ // 注意这种写法,虽然T确实是多个类型,但也要写成: T...
sizeof...(T) == sizeof...(params); // params也代指:很多变量5
}
func<>(); // 输出0
func<char, int, const char*>('a', 123, "abc"); // 输出3
' 上面代码的params 是形参,你也可以使用 实参(引用) '
template <typename... T>
void func(const T&... params){ // 注意写法(params便是 一堆实参)
}
我们上面,得到了 params,他里面有: 很多 不同类型的 变量。
但我们,无法 拆开,得到 每一个
要想拆开获取每一个,通常使用“调用 递归函数” 的方法
首先思考,下面这个样例:
template <typename... T>
void func(T... params){ 111 }
template <typename T1, typename... T2>
void func(T1 fir, T2... sec){ 222 }
两个“同名的函数”,比如我们调用: func(1,2,3) 他会调用哪个呢?
我们发现,其实两个函数 符合你func(1,2,3)的调用!!!
所以,“产生了冲突”,最好不要这样写代码!!
' 即(1,2,3),他既符合(T...),也符合(T1, T2...)!!! '
但当我们调用: func()时, 肯定是111这个函数
因为,对于222函数,他的T1不是可变长,所以他的参数 至少是>=1个!!!
' 即核心思想是: 将“一堆数据(a,b,c)” 分成 “一个数据(a) + 一堆数据(b,c)” '
void func(){ } ' 核心!!! 递归的终止 '
template <typename T1, typename... T2>
void func(T1 fir, T2... sec){
DE(fir); // 处理 “单个数据fir”
func( sec... ); // 注意,这里很奇怪...
/ 我们知道,sec就是指“一堆变量”,因为sizeof...(sec)的用法
/ 可能是sizeof...()函数比较特殊?? 总之这里要写成: sec...
}
两个问题:
1, 是否可以把func 写成 func(T t),即接收1个参数 作为 递归终止??
不可以!!
对于,空参调用 func<>(),则就报错了
2, 为什么 递归终止是: void func(){}, 而不是个 模板函数??
/ 可以去看: 上面目录里的: “书写标准、空模板参数”
/ 因为: template<> void func<>(){} 是错误语法
/ 而且, void func(){} 是“普通函数”,他们是可以 共存的!!!