【C++11】可变参数模版

可变参数模板

基本语法及原理

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系列的接口均为模板可变参数。功能上兼容pushinsert系列,但是emplace还支持新玩法。

假设容器为container<T>emplace支持直接插入构造T对象的参数,这样有些场景会更高效一些,可以直接在容器空间上构造T对象。emplace_back总体而言更高效,推荐以后使用emplace系列替代insertpush系列。

第二个程序中我们模拟实现了listemplaceemplace_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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值