第21课 可变参数模板(2)_展开参数包

1. 可变参数模板函数

(1)递归函数方式展开参数包

  ①一般需要提供前向声明、一个参数包的展开函数和一个递归终止函数。

  ②前向声明有时可省略,递归终止函数可以是0个或n个参数

(2)逗号表达式和初始化列表方式展开参数包

  ①逗号表达式按顺序执行,返回最后一个表达式的值。

  ②initilizer_list可接受任意多个不同类型的参数。

  ③借助逗号表达式来展开包,并将返回的结果用于初始化initilizer_list。

【编程实验】展开可变参数模板函数的参数包

#include <iostream>
#include <tuple>
//#include <stdexcept>
using namespace std;

/************利用递归方式展开可变参数模板函数的参数包************/
//1.求最大值(可接受多个参数)

int maximum(int n)   //递归终止函数
{
    return n;
}

template<typename... Args>
int maximum(int n, Args... args) //递归函数
{
    return std::max(n, maximum(args...)); 
}

//2. 用可变参数模板函数模仿printf的功能
void Printf(const char* s)  //递归终止函数
{
    while(*s){
        if(*s == '%' && *(++s)!='%')
            throw std::runtime_error("invalid format string: missing arguments");
        
        cout << *s++;    
    }
}

template<typename T, typename... Args>
void Printf(const char* s, T value, Args...args) //递归函数,展开参数包(value + args...)
{
    while(*s){
        if(*s == '%' && *(++s)!='%'){ //找到格式控制符%
            cout << value;
            Printf(++s, args...); //call even when *s=0 to detect extra argument
            return;
        }
        
        cout << *s++; //"%d %s %p %f
"输出非格式控制字符,如空格。            
    }
    
    throw runtime_error("extra arguments provided to print");
}

//3. 使用tuple转化并分解参数包
template<std::size_t Index=0, typename Tuple>  //递归终止函数
typename std::enable_if<Index==std::tuple_size<Tuple>::value>::type  //返回值void类型 
print_tp(Tuple&& t) //tp: tuple
{    
}

template<std::size_t Index=0, typename Tuple>  //递归终止函数
typename std::enable_if<Index<std::tuple_size<Tuple>::value>::type  //返回值void类型 
print_tp(Tuple&& t) //tp: tuple
{
    cout << std::get<Index>(t) << endl;
    print_tp<Index+1>(std::forward<Tuple>(t));
}

template<typename... Args>
void print(Args... args)
{
    print_tp(std::make_tuple(args...)); //将args转为tuple
}

/************利用逗号表达式和初始化列表展开可变参数模板函数的参数包************/
template<typename T>
void printargs(T t)   //注意这不是递归终止函数!而是一个用来输出参数内容的函数
{
    cout << t << endl;
};

template<class... Args>
void expand(Args... args)
{
    //方法1:数组的初始化列表
    //int arr[] = {(printargs(args),0)...};//逗号表达式。包扩展为(printargs(args1),0)
                                         //(printargs(args1),0),...,(printargs(argsN),0)
                                         //计算每个逗号表达式,调用printargs()(在这里获得各个参数)
                                         //同时,每个逗号表达式结果得0,然后用N个0初始化arr。
    //方法2:利用std::initializer_list
    //std::initializer_list<int>{(printargs(args),0)...}; //比方法1简洁,且无需定义一个辅助的arr。
    
    //方法3:利用lambda表达式
    //[&args...]{std::initializer_list<int>{(cout << args << endl,0)...};}();
    [&]{std::initializer_list<int>{(cout << args << endl,0)...};}();
} 

int main()
{
    /*******************递归方式展开参数包******************************/
    //1. 求最大值
    cout << maximum(57, 48, 60, 100, 20, 18) << endl;
    
    //2. 模拟printf的功能
    int* pi = new int;
    Printf("%d %s %p %f
", 15, "This is Ace.", pi, 3.1415926); //15 This is Ace. 0x6a77f0 3.14159
    delete pi;
    
    //3.利用tuple分解参数包
    print(15, "This is Ace.", pi, 3.1415926);
    
    cout << endl;
    /*******************逗号表达式方式展开参数包******************************/
    expand(15, "This is Ace.", pi, 3.1415926);
    expand(57, 48, 60, 100, 20, 18);
    
    return 0;
}

2. 可变参数模板类

(1)模板递归特化方式展开参数包

  ①一般需要类声明、类定义和特化模板类

  ②特化模板类用于递归的终止

(2)继承(或组合)方式展开参数包

  ①一般需要类声明、类定义和特化模板类

  ②特化模板类用于递归的终止(可以是参数个数减少,或满足一个条件停止递归)

  ③模板类继承是递归定义的,直到父模板类的模板参数满足递归终止条件,如下图。也可以采用组合的方式进行递归。

        

【编程实验】展开可变参数模板类的参数包

#include <iostream>
using namespace std;

//1. 递归方式展开参数包
template<typename ...Args> struct Sum; //类模反的前向声明

template<typename First, typename ...Rest>  //类模板的定义
struct Sum<First, Rest...>
{
    enum{value = Sum<First>::value + Sum<Rest...>::value};
};

template<typename Last>  //递归终止类
struct Sum<Last>
{
    enum{value = sizeof(Last)}; //求出Last类的大小
};

//2. 继承方式展开参数包
template<typename ...Values> class Tuple; //前向声明

template<typename Head, typename ...Tail>
class Tuple<Head, Tail...> : private Tuple<Tail...>  //私有继承自tuple<Tail...> 
{
    typedef Tuple<Tail...> inherited; //父类    
protected:
    Head m_head;

public:
    Tuple(){}
    //构造函数
    Tuple(Head v, Tail...vtail): m_head(v),inherited(vtail...) //inherited(...)不是创建临时对象
    {                                                          //而是调用父类构造函数来初始化!
        
    }
    
    //获取head和tail
    Head head(){return m_head;} //获取head
    inherited& tail(){return *this;} //c++对象内存模型,this指向对象开始的
                                     //地址,也是父类开始的地方。
};

template<> class Tuple<>{}; //递归终止类

//3. 组合方式展开参数包
template<typename ...Values> class tup; //前向声明

template<typename Head, typename ...Tail>
class tup<Head, Tail...>
{
    typedef tup<Tail...> composited;
protected:
    composited m_tail;  //组合方式
    Head m_head;
public:
    tup(){}
    
    tup(Head v, Tail...vtail):m_head(v), m_tail(vtail...)
    {
        
    }
    
    Head head() {return m_head;}
    composited& tail(){return m_tail;}
};

template<>class tup<>{}; //递归终止类

int main()
{
    //1. 模板递归
    cout <<Sum<char, double, float, int>::value << endl; //17
    
    //2. 继承方式
    Tuple<int, float, string> t(41, 6.3, "nico");
    cout << t.head() << endl;        //41
    cout << t.tail().head() << endl; //6.3
    cout << t.tail().tail().head() << endl; //"nico"
    cout <<"&t:"<< &t <<", &t.tail:" <<&t.tail() << endl;
    
    //3.组合方式
    tup<int, float, string> tc(41, 6.3, "nico");
    cout << tc.head() << endl;        //41
    cout << tc.tail().head() << endl; //6.3
    cout << tc.tail().tail().head() << endl; //"nico"

    return 0;
}

3.可变参数模板的妙用

(1)可变参数模板的一个特性就是参数包中的模板参数可以是任意数量和任意类型。因此可以以一种更加泛化的方式去处理一些问题,从而消除重复的代码。

(2)可变参数模板实现泛化的delegate

(3)可变模版参数(variadic templates)是C++11新增的最强大的特性之一,也是C++11中最难理解和掌握的特性之一。它可变模版参数比较抽象,使用起来需要一定的技巧。

(4)可变模板参数不能直接作为变量保存起来,需要借助tuple保存起来。

【编程实验】可变参数模板的妙用

#include <iostream>
#include <tuple>
#include <typeinfo>
using namespace std;

class Test
{
    int a;
    int b;
    int c;
public:
    Test():a(0),b(0),c(0)
    {
        cout << "Test()"<< endl;
    }
    
    Test(int a, int b):a(a),b(b),c(0)
    {
        cout << "Test(int a, int b)"<< endl;
    }
    
    Test(int a, int b, int c):a(a),b(b),c(c)
    {
        cout << "Test(int a, int b, int c)"<< endl;
    }
    
    void add(int x, int y)
    {
        cout << "x + y = "<< x + y << endl;
    }
    
        void sub(int x, int y)
    {
        cout << "x - y = "<< x - y << endl;
    }
};

/*********************************************************************************************/
//1. 创建对象的工厂函数
//(可以消除因T带有多个不同参数的构造函数而导致需重载多个createInstance的问题。
template<class T, typename ...Args>
T* createInstance(Args&& ...args)
{
    return new T(std::forward<Args>(args)...);
}

/*********************************************************************************************/
//2. 利用可变参数模板实现类似C#中的委托功能
template<class T, class R, typename...Args>
class Delegate
{
private:
    T* m_t; //被委托对象
    R (T::*m_f)(Args...); //被委托对象的成员函数指针
public:
    Delegate(T* t, R (T::*f)(Args...)): m_t(t), m_f(f){}
    
    R operator()(Args&&... args)
    {
        return (m_t->*m_f)(std::forward<Args>(args)...);
    }
};

//创建委托对象
template<class T, class R, typename...Args>
Delegate<T, R, Args...>  //返回值
createDelegate(T* t, R (T::*f)(Args...))
{
    return Delegate<T, R, Args...>(t, f);
}

/*********************************************************************************************/
//3. 创建整数序列(如IndexSeq<0,1,2,3,4,5>)
template<int...>
struct IndexSeq{};  //定义整数序列

//继承方式展开参数包
//说明:
//(1)也可以不用继承。采用using来实现,如using type = MakeIndexs<N-1, N-1, Indexs...>::type)
//(2)MakeIndexs<N-1, Indexs..., N-1>:第1个为N-1递归终止条件,第2个N-1表示将其放入参数包indexs的最后面,
//     即每次更小的数会被放入参数包的最后面,所以呈降序。
//(3)MakeIndexs<N-1, N-1, Indexs...> ,第1个N-1表示递归终止条件,第2个N-1将其放入参数包indexs的前面,
//     即更小的数会被放入参数包的前面。因此,呈升序序列。
template<int N, int... Indexs> 
struct MakeIndexs : MakeIndexs<N-1, N-1, Indexs...>{}; //private继承,升序序列
//struct MakeIndexs : MakeIndexs<N-1, Indexs..., N-1>{}; //降序序列

//特化模板:终止递归
template<int...Indexs>
struct MakeIndexs<0, Indexs...>
{
    typedef IndexSeq<Indexs...> type; 
};

/*********************************************************************************************/
//4. 利用整数序列输出可变模板参数的内容
//tuple在模版元编程中的一个应用场景是将可变模板参数保存起来,
//因为可变模板参数不能直接作为变量保存起来,需要借助tuple保存起来,
//保存之后再在需要的时候通过一些手段将tuple又转换为可变模板参数,
//这个过程有点类似于化学中的“氧化还原反应”。

template <typename T>
void print(T t)
{
    cout << t << endl;
}

template <typename T, typename...Args>
void print(T t, Args... args)
{
    print(t);
    print(args...);
}

template<int... Indexs, typename ...Args> //将Args...转为tuple,并生成整数序列。
void print_helper(IndexSeq<Indexs...>, std::tuple<Args...>&& tup)
{
    print(std::get<Indexs>(tup)...); //打印这个tuple
                                     //展开后,得get<0>(tup),...,get<N-1>(tup)
                                     //将tup的内容还原成一个可变参数,传到print去输出
}

//先将可变模板参数保存到tuple中,然后再通过元函数MakeIndexes生成一个整形序列,
//这个整形序列就是IndexSeq<0,1,2>,整形序列代表了tuple中元素的索引。
//再调用print_helper,展开这个整形序列,并根据具体的索引从tuple中获取对应的元素
//最终将从tuple中取出来的元素组成一个可变模板参数,从而实现了tuple“还原”为可变模板参数,
//最终调用print打印可变模板参数
template<typename ...Args>
void printargs(Args&&... args)
{
    print_helper(typename MakeIndexs<sizeof...(args)>::type(), std::make_tuple(args...));
}

int main()
{
    //创建对象的工厂函数
    Test* p1 = createInstance<Test>();
    Test* p2 = createInstance<Test>(1, 2);
    Test* p3 = createInstance<Test>(1, 2, 3);
    
    //利用可变参数模板实现类似C#中的委托功能
    Test t;
    auto d1 = createDelegate(&t, &Test::add); //创建委托
    d1(1,2);  //调用委托
    
    auto d2 = createDelegate(&t, &Test::sub);
    d2(5,3);
    
    //创建整数序列
    using T = MakeIndexs<3>::type;
    cout << typeid(T).name() << endl;
    
    //利用整数序列输出可变模板参数的内容
    print(1, 2.5, "test");   
    printargs(1, 2.5, "test"); //与print的区别:printargs会将可变参数保存起来,而
                               //前者不会。
    return 0;
}
原文地址:https://www.cnblogs.com/5iedu/p/7787302.html