第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\n"输出非格式控制字符,如空格。
    }

    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\n", 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;
}
时间: 2024-09-29 22:52:53

第21课 可变参数模板(2)_展开参数包的相关文章

第20课 可变参数模板(1)_模板参数包和函数参数包

1.  参数包(parameter pack) (1)模板参数包(以tuple为例):template<typename- Elements>class tuple ①Elements标识符的左侧使用了省略号,在C++11中Elements被称为"模板参数包",表示可以接受任意多个参数作为模板参数. ②编译器将多个模板参数打包成"单个"的模板参数包,如tuple<int, char, double>实例化模板类时,Element就是包含int

第23课 可变参数模板(4)_Optional和Lazy类的实现

1. optional类的实现 (1)optional的功能 ①optional<T>的内部存储空间可能存储了T类型的值,也可能没有.只有当optional被T初始化之后,这个optional才是有效的.否则是无效的.它实现了未初始化的概念. ②optional可以用于解决函数返回无效值的问题.当函数返回一个未初始化的Optional对象时,表明函数正确执行了,只是结果不是有用的值. ③举例:optional<int> op; //未被初始化. optional<int>

可变参数模板用法

//可变参数模板 //可变参数模板,可以创建可接受可变数量参数的模板函数和模板类 //本程序通过模板函数来实例一下可变参数模板的基本用法 #include<iostream> using namespace std; void one(){}//当最后一个参数传完后,需要一个无参的重载版本 template <typename T>//当只剩最后一个参数时,编译器优先选择此模板,这样最后一个输出后面就没有逗号了 void one(T v) { cout << v <

c++11可变参数模板的使用1

1.概述 C++11的新特性--可变模版参数(variadic templates)是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数.任意类型的参数.相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进.然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以它也是C++11中最难理解和掌握的特性之一. 虽然掌握可变模版参数有一定难度,但是它却是C++11中最有意思的一个特性,本文希望带领读者由浅入深的认识和掌握这一

c++11 可变参数模板函数

c++11 可变参数模板函数 #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <vector> #include <map> // 在C++11之前,类模板和函数模板只能含有固定数量的模板参数.C++11增强了模板功能,允许模板定义中包含0到任意个模板参数,这就是可变参数模板. // 可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变

C++的可变参数模板函数

可变参数模板函数写法: 模板参数里写typename... args,表明args是一个可变参数. 之后再函数参数里args后面也要加...,以表示该参数为可变参数. 函数参数中对于args的修饰,会扩展到所有该args的参数,比如下面代码: //可变参数模板函数使用方法1:递归调用,每次将可变参数规模变小直到为0 template<typename T> void print(const T& x) { cout << "最后一次调用print,输出:"

C++ 11 可变参数模板和 boost::any 实现可变参数函数

1 class SqlHelper 2 { 3 public: 4 template <typename... Params> 5 static bool preparedExecute(sql::PreparedStatement* pstmt, Params... parameters) 6 { 7 return doPreparedExecute(pstmt, 1, parameters...); 8 } 9 10 private: 11 template <typename...

Spark IMF传奇行动第21课:从Spark架构中透视Job

版权声明:本文为博主原创文章,未经博主允许不得转载.作者:HaiziS 昨晚听了王家林老师的Spark IMF传奇行动第21课:从Spark架构中透视Job,笔记如下: 默认一个worker有一个executor,也可以设置多个,当cpu利用不足时. 并行度也是被继承的 当Spark集群启动的时候,首先启动Master进程负责整个集群资源管理和分配并接受作业的提交且为作业分配计算资源,每个工作节点默认启动一个Worker Process来管理当前节点的mem,cpu等计算资源并且向Master汇

C++反射机制:可变参数模板实现C++反射

1. 概要   本文描述一个通过C++可变参数模板实现C++反射机制的方法.该方法非常实用,在Nebula高性能网络框架中大量应用,实现了非常强大的动态加载动态创建功能.Nebula框架在coding.net的仓库地址.   C++11的新特性--可变模版参数(variadic templates)是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数.任意类型的参数.关于可变参数模板的原理和应用不是本文重点,不过通过本文中的例子也可充分了解可变参数模板是如何应用的.