# Template编程
# 1.NoneType Arguments (opens new window)
常规模板编程时的参数Arguments
是带类型的,形如
template<typename T1, typename T2, ..., typename Tn>
void func(...)
{
}
除了向上面这样使用类型参数外,还可以使用非类型参数,
template<typename T, int N>
T* func()
{
T a[N];
return a;
}
void main(void)
{
auto p = func<int,4>();
}
第二个模板参数是指定了int
类型,除此之外,还能给定初始值,形如:
template<typename T, int N=5>
...
这种使用方法在C++
标准模板库STL
中也比较常见,如array
的实现部分,
template<typename _Tp, std::size_t _Nm>
struct array
{
...
}
# 2.class 与 typename的区别
在定义模板时,经常会class
与typename
混合使用,其实class
是更早的定义template
的方法,后来引入了typename
。
大部分情况下typename
和class
是通用了,但在需要告诉编译器一个变量是类型名时,需要使用typename
而不能使用class
。
template<class T>
T add(T x, T y) { return x + y; }
template<typename T>
T add(T x, T y) { return x + y; }
// Both cases are OK.
template<typename T>
struct basic_type { using type = T; }
template<typename T>
using Type = typename basic_type<T>::type; // has to use typename instead of class
# 3.template parameter and argument
一个模板的定义包括由1到多个模板参数,通常模板的声明形式如下:
template < parameter-list > declaration;
参数列表parameter-list
中的参数可能为:
- non-type 模板参数
- 类型模板参数
- 模板模板参数
// non-type模板参数
template<int n>
struct B { /* ... */ };
// 类型模板参数
template<class T>
class My_vector { /* ... */ };
// 模板模板参数:
template<typename T>
class my_array {};
// two type template parameters and one template template parameter:
template<typename K, typename V, template<typename> typename C = my_array>
class Map
{
C<K> key;
C<V> value;
};
template argument
是实例化模板时传入的值,parameter
的含义是指变量,也就是常说的形参,argument
是传入的值,也就是常说的实参。
# 3.Template
中的打包参数typename...Args
模板参数打包template parameter pack
是指能够接受零个或多个模板参数的变量,同函数一样,函数参数打包是能够接受零或多个参数的变量。
参数打包的展开方式为pattern...
。
在类模板中打包参数只能在模板参数的最后出现,
template<typename U, typename... Ts> // OK: can deduce U
struct valid;
在模板函数中,使用因为类型可以从函数声明中推断,因此打包参数可以不总在参数最后出现。
template<typename... Ts, typename U, typename=void>
void valid(U, Ts...); // OK: can deduce U
// void valid(Ts..., U); // Can't be used: Ts... is a non-deduced context in this position
valid(1.0, 1, 2, 3); // OK: deduces U as double, Ts as {int, int, int}
对变参函数的常用操作:
sizeof...
查看参数个数- 打印变参中的每个参数
template<typename... Ts>
void func(Ts... args)
{
const int size = sizeof...(args) + 2;
int res[size] = {1, args..., 2};
// since initializer lists guarantee sequencing, this can be used to
// call a function on each element of a pack, in order:
int dummy[sizeof...(Ts)] = {(std::cout << args, 0)...};
}
一个使用参数打包的例子,
template<typename T>
class Creator;
template<typename Ret, typename... Args>
class Creator<Ret(Args...)>
{
public:
using FuncType = std::function<Ret(Args...)>;
explicit Creator(std::string_view name, FuncType func) noexcept :
name_(name), func_(func) {}
Ret Create(Args... args) { return func_(args...);}
private:
FuncType func_;
std::string_view name_;
};
int add(int x, int y)
{
return x + y;
}
void main(void)
{
std::string_view s("add1");
Creator<int(int,int)> creator(s, add);
int r = creator.Create(1, 2);
std::cout << r << "\n";
}
通过上面这种方式可以创建一个函数容器,用来作为工厂模式的创建器,以包含工厂方法。
# 4.模板编译
当编译器遇到一个模板定义时,它并不生成代码。只有在实例化出一个模板的特定版本时,编译器才会生成代码。当使用模板而不是定义模板时,编译器才生成代码,这一特性会影响代码如何组织代码以及错误何时被检出。
在调用一个函数时,编译器只需要掌握函数的声明。类似,在使用一个类类型时,类定义必须可用,但成员函数只需要声明,不一定定义。因此,在编写代码时,通常可以将类定义和函数声明放在头文件中,而普通函数和类的成员函数放在源文件中。
而对于模板来说,为了生成一个实例化的版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常既包括声明也包括定义。