可变参数模板
基本语法及原理
C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包,存在两种参数包:模板参数包,表示零或多个模板参数;函数参数包,表示零或多个函数参数。
template <class...Args> void Func(Args... args) {}
template <class...Args> void Func(Args&... args) {}
template <class...Args> void Func(Args&&... args) {}
我们用省略号来指出一个模板参数或函数参数表示一个包。
- 在模板参数列表中,
class...
或typename...
指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟...
指出接下来表示零或多个形参对象列表。 - 函数参数包可以用左值引用或右值引用表示,跟前面普通模板一样,每个参数实例化时遵循引用折叠规则。
可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
这里我们可以使用sizeof...
运算符去计算参数包中参数的个数。
template <class...Args>
void Print(Args&&... args)
{
cout << sizeof...(args) << endl;
}
int main()
{
double x = 2.2;
Print(); // 包里有0个参数
Print(1); // 包里有1个参数
Print(1, string("xxxxx")); // 包里有2个参数
Print(1.1, string("xxxxx"), x); // 包里有3个参数
return 0;
}
原理1:编译本质这里会结合引用折叠规则实例化出以下四个函数
void Print();
void Print(int&& arg1);
void Print(int&& arg1, string&& arg2);
void Print(double&& arg1, string&& arg2, double& arg3);
原理2:更本质去看,没有可变参数模板,我们需实现多个函数模板才能支持这里的功能。有了可变参数模板,它在类型泛化基础上叠加数量变化,让泛型编程更灵活。
void Print();
template <class T1>
void Print(T1&& arg1);
template <class T1, class T2>
void Print(T1&& arg1, T2&& arg2);
template <class T1, class T2, class T3>
void Print(T1&& arg1, T2&& arg2, T3&& arg3);
// ...
包扩展
对于一个参数包,我们除计算其参数个数外,能做的就是扩展它。
扩展包时,要提供用于每个扩展元素的模式,扩展操作是将包分解为元素,对每个元素应用模式以获得扩展后的列表,通过在模式右边放省略号(...
)触发扩展操作,底层实现细节如下图所示。
C++还支持更复杂的包扩展,可直接将参数包依次展开作为实参给一个函数处理。
// 可变模板参数
// 参数类型可变
// 参数个数可变
// 打印参数包内容
//template <class...Args>
//void Print(Args... args)
//{
// // 可变参数模板编译时解析
// // 下面是运行获取和解析, 所以不支持这样用
// cout << sizeof...(args) << endl;
// for (size_t i = 0; i < sizeof...(args); i++)
// {
// cout << args[i] << " ";
// }
//
// cout << endl;
//}
void ShowList()
{
// 编译器时递归的终止条件, 参数包是0个时, 直接匹配这个函数
cout << endl;
}
template <class T, class...Args>
void ShowList(T x, Args... args)
{
cout << x << " ";
// args是N个参数的参数包
// 调用ShowList, 参数包的第一个传给x, 剩下N-1传给第二个参数包
ShowList(args...);
}
// 编译时递归推导解析参数
template <class...Args>
void Print(Args... args)
{
ShowList(args...);
}
int main()
{
Print();
Print(1);
Print(1, string("xxxxx"));
Print(1, string("xxxxx"), 2.2);
return 0;
}
//template <class T, class...Args>
//void ShowList(T x, Args... args)
//{
// cout << x << " ";
// ShowList(args...);
//}
// Print(1, string("xxxxx"), 2.2);调用时
// 本质编译器将可变参数模板通过模式的包扩展, 编译器推导的以下三个重载函数函数
//void ShowList(double x)
//{
// cout << x << " ";
//
// ShowList();
//}
//void ShowList(string x, double z)
//{
// cout << x << " ";
// ShowList(z);
//}
//void ShowList(int x, string y, double z)
//{
// cout << x << " ";
// ShowList(y, z);
//}
//void Print(int x, string y, double z)
//{
// ShowList(x, y, z);
//}
// 以下代码为展示包扩展的另一种形式
template <class T>
const T& GetArg(const T& x)
{
cout << x << " ";
return x;
}
template <class...Args>
void Arguments(Args... args)
{}
template <class...Args>
void Print(Args... args)
{
// 注意GetArg必须返回或者到的对象,这样才能组成参数包给Arguments
Arguments(GetArg(args)...);
}
// 本质可以理解为编译器编译时,包的扩展模式
// 将上⾯的函数模板扩展实例化为下⾯的函数
// 是不是很抽象,C++11以后,只能说委员会的⼤佬设计语法思维跳跃得太厉害
//void Print(int x, string y, double z)
//{
// Arguments(GetArg(x), GetArg(y), GetArg(z));
//}
int main()
{
Print(1, string("xxxxx"), 2.2);
return 0;
}
emplace系列接口
template <class... Args>
void emplace_back (Args&&... args);
template <class... Args>
iterator emplace (const_iterator position, Args&&... args);
C++11以后STL容器新增了emplace系列的接口,emplace系列的接口均为模板可变参数。功能上兼容push
和insert
系列,但是emplace
还支持新玩法。
假设容器为container<T>
,emplace
还支持直接插入构造T对象的参数,这样有些场景会更高效一些,可以直接在容器空间上构造T对象。emplace_back
总体而言更高效,推荐以后使用emplace系列替代insert
和push
系列。
第二个程序中我们模拟实现了list
的emplace
和emplace_back
接口,这里把参数包不断往下传递,最终在结点的构造中直接去匹配容器存储的数据类型T的构造,所以达到了前面说的emplace
支持直接插入构造T对象的参数,这样有些场景会更高效一些,可以直接在容器空间上构造T对象。
传递参数包过程中,如果是Args&&... args
的参数包,要用完美转发参数包,方式如下std::forward<Args>(args)...
,否则编译时包扩展后右值引用变量表达式就变成了左值。
#include<list>
int main()
{
list<string> lt;
// 传左值,跟push_back一样,走拷贝构造
string s1("111111111111");
lt.emplace_back(s1);
cout << "*********************************" << endl;
// 右值,跟push_back一样,走移动构造
lt.emplace_back(move(s1));
cout << "*********************************" << endl;
// 直接把构造string参数包往下传,直接用string参数包构造string
// 这里达到的效果是push_back做不到的
lt.emplace_back("111111111111");
cout << "*********************************" << endl;
list<pair<bit::string, int>> lt1;
// 跟push_back一样
// 构造pair + 拷贝/移动构造pair到list的节点中data上
pair<string, int> kv("苹果", 1);
lt1.emplace_back(kv);
cout << "*********************************" << endl;
// 跟push_back一样
lt1.emplace_back(move(kv));
cout << "*********************************" << endl;
// 直接把构造pair参数包往下传,直接用pair参数包构造pair
// 这里达到的效果是push_back做不到的
lt1.emplace_back("苹果", 1);
cout << "*********************************" << endl;
return 0;
}
// List.h
namespace bit
{
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(T&& data)
:_next(nullptr)
,_prev(nullptr)
,_data(std::forward<T>(data)) {}
template <class... Args>
ListNode(Args&&... args)
:_next(nullptr)
,_prev(nullptr)
,_data(std::forward<Args>(args)...) {}
};
template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node;
ListIterator(Node* node)
:_node(node) {}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Ref operator*()
{
return _node->_data;
}
bool operator!=(const Self&it)
{
return _node != it._node;
}
};
template<class T>
class List
{
typedef ListNode<T> Node;
public:
typedef ListIterator<T, T&, T> iterator;
typedef ListIterator<T, const T&,const T> const_iterator;
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
void empty_init()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
List()
{
empty_init();
}
void push_back(T&&x)
{
insert(end(),std::forward<T>(x));
}
iterator insert(iterator pos,T&& x)
{
Node* cur = pos._node;
Node* newnode = new Node(std::forward<T>(x));
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
template <class... Args>
void emplace_back(Args&&...args)
{
insert(end(),std::forward<Args>(args)...);
}
template <class...Args>
iterator insert(iterator pos, Args&&...args)
{
Node* cur = pos._node;
Node* newnode = new Node(std::forward<Args>(args)...);
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
private:
Node* _head;
};
}
// Test.cpp
#include"List.h"
int main()
{
bit::list<bit::string> lt;
// 传左值,跟push_back一样,走拷贝构造
bit::string s1("111111111111");
lt.emplace_back(s1);
cout << "*********************************" << endl;
// 右值,跟push_back一样,走移动构造
lt.emplace_back(move(s1));
cout << "*********************************" << endl;
// 直接把构造string参数包往下传,直接用string参数包构造string
// 这里达到的效果是push_back做不到的
lt.emplace_back("111111111111");
cout << "*********************************" << endl;
bit::list<pair<bit::string, int>> lt1;
// 跟push_back一样
// 构造pair + 拷贝/移动构造pair到list的节点中data上
pair<bit::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
cout << "*********************************" << endl;
// 跟push_back一样
lt1.emplace_back(move(kv));
cout << "*********************************" << endl;
// 直接把构造pair参数包往下传,直接用pair参数包构造pair
// 这里达到的效果是push_back做不到的
lt1.emplace_back("苹果", 1);
cout << "*********************************" << endl;
return 0;
}