看到QT的源码,梳理下模板相关问题分解和设计思路。
- 模板与函数的对应关系
template<typename T> //入参:一个多个,模板形参所允许的类型
struct SomeFunc // 函数名称:对应声明的类名
{
static const int value = some_value; //返回值:通过该函数调用获得的类型或数值结果;
using type = SomeType;
};
对某些函数中的“函数”不只用于形参列表的推导,也可用于形参的保存,对整个功能实现起辅助作用。
而且通常模板(函数)的原型声明不能够解决问题,而必须借助模板特化来达到目的。如:
//1. 中间类型定义,仅做索引缓存用,无函数的计算输出能力
template <size_t... args> struct IndexList;
//2. 函数的原型声明
struct IndexJoin<class IL1, class IL2>;
//3. 借助模板特化及编译器的类型推导和变参的合并能力达到目的
template <size_t... idxs1, size_t... idxs2>
struct IndexJoin<IndexList<idxs1...>, IndexList<idxs2...>>
{
using type = IndexList<idxs1..., idxs2...>;
};
- 实际问题的拆解推导过程
解决的问题:
从参数列表中取前N个;
拆解和推导过程:
1 通过模板特化,可以从完整的模板形参列表中取第1个,并且能够实现变参模板的形参拼接;
2 逐次从变参列表中取第一个进行拼接,则可获取到前N个形参的列表。
3 定义拆分及拼接的辅助模板,定义主模板调用辅助模板;
/*
The following List classes are used to help to handle the list of arguments.
It follow the same principles as the lisp lists.
List_Left<L,N> take a list and a number as a parameter and returns (via the Value typedef,
the list composed of the first N element of the list
*/
// With variadic template, lists are represented using a variadic template argument instead of the lisp way
//1. 定义形参列表,用于形参的缓存,辅助功能实现
template <typename...> struct List {};
//2. 借助模板特化获取List的第一个形参,本质上也可以单独定义一个模板,用于取Head
template <typename Head, typename... Tail> struct List<Head, Tail...> { typedef Head Car; typedef List<Tail...> Cdr; };
//3. Append的函数的声明,输入为两个List,输出为拼接后的List
template <typename, typename> struct List_Append;
//输入参数:L1, L2
template <typename... L1, typename...L2>
struct List_Append<List<L1...>, List<L2...>> {
//返回值:Value
typedef List<L1..., L2...> Value;
};
//4. 取前N个,可以理解为对前面定义的拆分函数、拼接函数的调用;
// 调用方式是放在模板的形参列表中;
template <typename L, int N> struct List_Left {
//辅助函数调用
typedef typename List_Append<List<typename L::Car>,typename List_Left<typename L::Cdr, N - 1>::Value>::Value Value;
};
// 边界定义
template <typename L> struct List_Left<L, 0> { typedef List<> Value; };
所以,模板设计时要把核心要解决的问题想清楚,然后看模板化下编译能够提供什么的能力,然后结合这些能力对问题进行细化拆解,定义相关的辅助函数,然后定义主函数调用辅助函数,最终达到预期效果。