CPP {模板函数,可变长模板函数,函数模板特化}
模板函数
定义
在预编译阶段, 扫描*.cpp
里出现的所有实例函数(比如a.cpp有<int>, b.cpp里有<string>
), 则会实例化<int>, <string>
;
.
当然a.cpp
里有<int>
, 那么你要保证 a.cpp
里 至少要有<class T>
的声明;
性质
用模板的一个好处, 自己不用包含头文件 由用户去包含, 参见DATA::DB::Select
的设计;
@DELI;
#特化模板函数 与 重载函数, 的使用区别#;
对于Get_bitCount( int/int64/...)
(参数必须是整型), 这个场景, 用模板呢? 还是用重载呢?
用模板好, 即template<T> int F(); (只声明不实现); template<> int F<int>(int){...} template<> int F<int64>(int64){...}
;
因为 他们的代码底层/函数参数个数是完全一致的, 就是求二进制1
的个数;
如果用重载, 会有如下问题: 如果你不小心修改了某一个函数的名称, 此时不会报错 (而如果是模板函数 他会报错), 这不好 因为他们就是同一个函数逻辑, 名称应该都一样;
.
一般, 如果你的函数参数不同, 此时用重载, 比如Replace( string & _cur, int _l, int _r, string const& _new)
和 Replace( string & _cur, const string & _raw, const string & _new)
, 函数参数不同, 实际上 代码底层 也是不同的, 一个是把[l-r]
替换, 一个是把所有的raw
字串给替换;
@DELI;
函数可以缺省模板参数, template<T,V> void F(...)
你写F<T>(...)
他可以根据...
参数 来可以推导V;
@DELI;
#变长模板函数: <class ... T>
#;
宏的参数 可以是变长, 模板函数也可以是变长的 即参数个数可以任意 (普通函数不能变长, 必须是模板函数);
template< class _T> void D( _T _t){ cout<< _t;}
template< class _H, class... _T> void D( _H _h, _T... _t){ cout<< _h << ","; D( _t ...);}
@DELI;
错误
template< class _t, std::size_t _siz> struct __IsContainer_Unwrap<std::array<_t,_siz> > : std::true_type{};
如果你写成int _siz
这是错误的, 因为std::array
她模板类的定义 就是用的size_t
;
@DELI;
#模板函数 声明和定义不可以分开, 必须实现要写到hpp里面#
看以下代码
g.h: template< class _T_> extern void F( _T_);
b.cpp:
template< class _T_> void F( _T_ a){ DE_(_a;}}
void G(){ F(1);}
a.cpp:
int main(){
F(1);
}
他是没问题的, 是因为 b.cpp
里 你调用了F(1)
他实例化了F<int>
; 所以你a.cpp
里可以访问F<int>
;
.
但是 如果你a.cpp
里调用F( 1.2)
这就报错了, 说明你b.cpp
里对F()
函数的实现 并没有用, a.pp
里是用不了的!
因此 模板函数的声明实现 必须放一起;
.
但有个疑问: std::sort
他的实现放哪了? (可能因为他是动态库 所以情况不一样?)
@DELI;
#模板函数与普通函数 不要同时使用#
void Cout();
template< class _H, class... _T> void Cout( _H _h, _T... _t){ cout<< _h; Cout(_t...);}
不要写这种代码, 因为 一个是普通函数 一个是模板函数, 这导致: 模板函数他的实现在HPP文件里, 而普通函数他的声明在HPP 而实现在CPP里面, 这就很别扭 明明是同一个函数 可却分开了;
改成以下形式:
template< class... _Zero_> void Cout( _Zero_... _a){ assert(sizeof...(_a)==0); } // 接受空参;
template< class _One_> void Cout( _One_ _a){ cout<<_a;} // 1个参数
template< class _H, class... _T> void Cout( _H _h, _T... _t){ cout<< _h; Cout(_t...); // >=2 个参数;
这样, 他们都是模板参数, 直接放到头文件里面即可, 不用分开了;
@DELI;
模板函数 他的作用域是当前文件, 即他和静态函数是一样的, 因此 不支持链接;
比如: A.cpp
里 你声明了template<T> void F(T);
, 然后在B.cpp
里 你实现了template<T> void F(T){ ...}
; 于是你把他俩链接到一起, 发现 你在A.cpp
里面 还是无法调用F(T)
函数;
.
因为F(T)
函数的实现 他只生效于B.cpp
里面; (如果他不是模板函数 而是普通函数, 这没问题, 可是对于模板函数 他不支持链接!);
可变长模板函数
性质
template< int... _a>
, 对于常量 也可以; 即调用是<>, <1>, <1,2>, ...
;
@DELI:
参数可以为空;
即template< class... T> void F( T... _t);
执行F()
是可以的, 此时sizeof...(_t) == 0
;
@DELI;
额外参数 要写在开头, 即template< class _H, class... _T> string F( int a, const _H & _h, const _T & ... _t)
, int a
要在开头, 此时递归终止函数是F(int){}
;
@DELI;
空参函数(即递归终止函数), 必须要写;
即便你写成以下代码
template< class _H, class... _T> string F( const _H & _h, const _T & ... _t){
if( sizeof...( _t) > 0){ F( _t...);}
}
.
你可能认为, 递归终止是(_h, 空)
不需要写一个F()
的函数; 这是错误的, 会报错, 因為F
的函數定義 是1 + >=0
即>=1
個參數, 可以編譯器看到F( _t...)
這個參數_t...
是表示>=0
個參數, 即F(_t...)
要匹配一個接受>=0
個參數的F
函數; 总之 你必须写一个F(){}
的普通函数 作为递归终止;
.
因此, 你這個if
判斷 可以去掉;
@DELI;
#sizeof...
获取参数个数#
template< class... T>
void F( const T &... _arr){
`sizeof...( _arr)`等于`...`里的参数个数;
}
F( 1, .0, "abc')的参数个数为3
;
模板函数的全特化
性质
特化, 他必须完全和模板函数的参数 完全对应;
比如模板是template< T> T* Func( T const&&)
, 如果你要对T== int*
进行特化, 就等价于 让模板中的T
全部替换成int*
, 即特化是template<> int* * Func< int* >( int* const&&);
(修饰符一个都不能差 比如把const丢掉就错了);
@DELI;
特化函数 必须放在模板函数的下面 (否则你特化 不知道是对谁特化呀…)
模板函数可以直接调用其特化函数, 有点像类函数 但不完全一样;
template< class _T> void F(){ F<int>(); F<string>();}
template<> void F<int>(){ DE_(2);}
template<> void F<string>(){ DE_(3); F<int>();}
F()模板函数 可以调用其特化; 但是你在`F<int>`是不能调用`F<string>`的;
但注意 如果你写成再添加template<T,F> void F()`, 他不是F<T>的特化! 他是函数重载;
`.` 因此, 虽然说大多情况下 模板函数不用采用*声明+定义分离*的形式(即模板函数都是放在头文件里面的), 但是模板函数的声明 还是有用处的, 一个函数F 他有若干个模板函数`<T>, <T,V>, ...` 他们之间要相互调用 此时就需要声明;
@DELI;
函数只能全特化, 不能偏特化;
错误
类的(静态/非静态)函数, 虽然允许是模板函数, 但不允许特化! (只有普通函数允许特化);
他会报错 error: explicit specialization in non-namespace scope 'struct ST'
;
补救措施是: 使用函数重载 来替换特化, 即把成员函数写成 模板函数 + 非模板函数重载, 如下:
template< class _T> struct __toString<_T, 0>{ // Atom
template< class _t> static string TOstring( _t const& _v){ ...}
static string TOstring( unsigned __int128 const& _v){ ...} // 不能写成特化
};