`计算机知识` `C++` 全特化, 偏特化, template泛型模板编程,实参推演,可变长模板函数,模板函数的.h声明与.cpp实现

catalog

  • 默认模板参数
  • 函数模板的参数, 必须和(主模板)参数 完全匹配
    • 函数的 重载 与 全特化
  • 全特化, 偏特化
  • 模板函数的声明和定义, 必须在同个文件里
  • 无占位类型
    • std::is_same
  • -----------------------------------------------
  • 模板函数的.h声明与.cpp实现
    • 可变长模板函数
  • 函数模板
    • 类型模板参数
    • 非类型模板参数
    • 函数声明与实现
    • 特化与重载与规范 (重点)
      • 书写标准、空模板参数
  • 类模板
    • 传参
    • 模板成员函数
    • 特化
      • 全特化
      • 偏特化
        • 数量上的 偏特化
        • 类型范围上的 偏特化
  • 声明实现
  • 实参推演
  • 实例化
    • 重复实例化
  • 可变长参数
    • 可变长-函数模板参数
      • 参数包 的展开

默认模板参数

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){}

你写成: ( int & t) ( char t) ( int t)都是错的!!! 这里非常非常重要!!! 仔细理解
因为, 主模板函数 只有一个T, 你在全特化时( y t){}, 最终x 和 y 都会变成同一个T, 所以, xy, 必须严格相同

函数的 重载 与 全特化

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里, 有两种实现方式

  • 要么 A.h里是:
    template <class T> extern void Func( T t);
    
    template <class T> void Func( T t){ ...} 
    
    即, A.h 原封不动, 直接放到他下面即可
  • 要么 A.h 里是:
    template <class T> void Func( T t){ ...} 
    

两种写法都是完全等价的; 方式一看起来更简洁些.


为什么呢?

对于 非模板的函数, 他有两个步骤: (声明extern) 和 (定义/实现)

但是, 对于模板函数, 他有三个步骤: (声明) 和 (定义) 和 (实现)

你的template< T> Func( T t){ ... }, 其实是定义, 不是(实现)!!!

这个 (实现), 又分为两种

  • 1: 全特化, 即这个完整的(实现), 你已经实现了;
  • 2: 否则, 不管是(primary主模板函数) 还是 (partial偏特化模板函数), 它里面都有(模板), 不是真正的 函数实现!!!

对于第2种的 这个(实现), 有两点要注意:
1: 他是编译器 在 编译 生成.o文件 时, 自动帮我生成的
2: 你必须要有 (定义), 否则, 编译器也生成不了; 只有(声明) 不行!!

定义带有template实现将template变成具体的type (这个type取决于你的code), 这个过程, 称为 (实例化);

实例化, 发生在 (编译)阶段 <即生成单独的.obj文件>时, 而不是 (链接)阶段.
这句话的意思, 等价于: .obj文件里, 是没有template函数的!!! template都已经被 (实例化)成 具体类型了!!!
这句话, 非常重要!!! 下面会解释它


比如:

  • A.h: 模板声明 extern void Func( T t);
  • A.cpp: 模板定义 void Func( T t){ ... }
  • main.cpp: #include "A.h" Funt( int);

当我们在 main.cpp里, 调用: Funt( int);
这个函数, 我们并没有实现, 所以编译器会给我们实现; 我们知道, (实现)一个模板函数, 必须要得到他的 (定义)

而, main.obj这个文件, 他只有A.h 即(声明), 没有定义!!! 所以, 编译器会报错!!!

A.cpp 编译后的 A.obj文件, 会是 (NULL) 里面内容是空的!!!

一定要仔细理解: (模板函数的) 声明 和 定义, 必须是在 (同一个文件)里.
你在 A.cpp, 他在预处理后, 声明和定义, 不就是在 (同一个文件)里啊; 为什么会出错呢??
因为, 对于main.cpp, 他在预处理后 , 他的声明和定义 不在同一个文件里.

总之: 你的code 在使用 模板时, 最终必须找到 (具体的 实现代码);

  • 要么, 你自己写了 实现代码, 这肯定没问题
  • 要么, 编译器就会给你生成; 而他的生成, 必须 借助 (模板函数的 定义); 所以, 你必须保证: 在编译阶段, 他能在当前.cpp文件里, 找到 (模板的 定义)

无占位类型

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

std::is_same

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

-----------------------------------------------

模板函数的.h声明与.cpp实现

模板函数, 他的作用域 是本文件 (即和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 = intadd(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写成constconstexpr

函数声明与实现

我们知道,“ 头文件里,是不可以实现的,只能写定义!! ”
   函数: 只能写声明,不能实现!!! 
   	  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) {12都是int},符合的函数有: 2233
	/ 然后, 再“推演出 模板参数”
	/ 比如他尝试去匹配33:  因为(33函数参数里,没有模板参数!! 这是2int,类型是确定的)
	/ 即 没有推演出 模板参数!! 然而,33函数 是需要1个 “模板参数”的
	/  你选择没有模板参数,所以不匹配。
	/ 再去尝试匹配22:  因为(22函数参数是:T和U,两个模板参数)
	/ 因为,你的func(1,2),根据12的值,推演出是:(int, int)
	/ 即 T= int, U = int, “即,2个模板参数”,符合22函数
	/ 因此, func(1, 2) 会调用 22函数
	' 即,先对“函数参数 的 数目、类型”,进行匹配!!! (得到2233) '
	' 再对“模板参数 的 数目” 进行匹配(注意此时没有类型, 毕竟模板参数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){} 错!!
	/ 因为,floatdouble 类型不同!! 这你要对应“泛化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 包含了(intint*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(){} 是“普通函数”,他们是可以 共存的!!!

你可能感兴趣的:(计算机知识,c++,开发语言,后端)