可变参模板指南

news/2024/12/1 10:45:34/

可变参模板

文章目录

  • 可变参模板
    • 可变参函数模板
      • 基本外观和介绍
      • 展开参数包(获取参数包的值)
        • 错误的演示
        • 正确的演示
          • 采取递归方式
          • 采取逗号表达式
            • 为什么需要逗号表达式
          • 其他方式
    • 应用
      • 1.求最大值(可接受多个参数)
      • 2. 用可变参数模板函数模仿printf的功能
      • 3. 使用tuple转化并分解参数包
  • 获取可变参函数模板中的值
    • 可变参类模板
      • 基本外观和介绍
      • **三段式** :
      • 两段式:
    • 执行结果和原理分析
    • 如何获取参数包中的值
  • 组合+递归形式 实现
  • 泛型的应用
    • **4可变参数模版实现泛化的delegate**

可变参函数模板

基本外观和介绍

 #include<iostream>
using namespace std;
namespace detail1{
template <typename... N>    //<1>  void func(N... args)       //<2>
{cout << "func begin" << endl;cout << "函数形参包的参数数目"<<sizeof...(args) << endl;cout <<"类型形参包的参数数目"<< sizeof...(N) << endl;cout <<"func endl" << endl;
}
}int main()
{detail1::func();detail1::func(12, 34);detail1::func(12, 3.4, 34);detail1::func(12, 34, "34");
}
  • <1>这个省略号...其实我们可以理解为 *指针&引用 一样,都是用来描述一种参数类型,我们把带省略号的参数称为参数包,它里面包含了0到N(N>=0)个模版参数,例如上面的(typename... N) 形参N是一个类型参数包,而args函数形参参数包,并且这个类型参数包函数形参参数包无法直接使用.
  • <2> 我们可以使用 N... 来使用参数包, 表示将参数包逐个展开 .
    • 把省略号...加到参数包参数的右边可以理解为将当前参数包展开成了一个一个独立的参数

与其他参数名一样,可以将这些参数包的名称指定为任何符合C++标识符规则的名称,Args与T的区别在于,T与一种类型匹配,而Args与任意数量(包括0)的类型匹配。
更准确的说,函数参数包args包含的值列表与模板参数包Args包含的类型列表匹配——无论是从数量还是类型上!

  • 这里用到了sizeof...运算符,当我们需要知道包中有多少个元素的时候,可以使用sizeof...运算符返回一个常量。

输出结果:

func begin
函数形参包的参数数目0
类型形参包的参数数目0
func endl
func begin
函数形参包的参数数目2
类型形参包的参数数目2
func endl
func begin
函数形参包的参数数目3
类型形参包的参数数目3
func endl
func begin
函数形参包的参数数目3
类型形参包的参数数目3
func endl

展开参数包(获取参数包的值)

  • 我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。
template <class ...Args>
void ShowList(Args... args)
{// 获取、解析可变参数
}
int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

错误的演示

  • 1 并不支持这样使用:
template <class ...Args>
void ShowList(Args... args)
{cout << sizeof...(Args) << endl;cout << sizeof...(args) << endl;for (int i = 0; i < sizeof...(args); i++){// 无法编译,编译器无法解析cout << args[i] << " ";    }
}
  • 2 可以将省略号放在函数参数包名字的右边,将参数包展开:
template<class T>
void showlist(Args...args)
{showlist(args...);
}

但是这样调用存在缺陷,假如有这样的调用:

showlist(6,'L',0.6);

这将把6、‘L’、0.6 封装到args中,在该函数内部,下面的调用:

showlist(args...);

将展开成这样:

showlist(5,'L',0.5);

该函数调用与原始函数调用相同,因此他将使用相同的参数不断的调用自己,导致无限递归。

正确的演示

采取递归方式
 // 同名递归终止函数,位置放在 参数包展开函数的前面void func() // 当 OtherArgs为NULL空参数 时, 调用这个函数,实际上这个函数只是为了让下面的变参函数能调用到最后的无参数的func(){cout << "递归终止函数被调用" << endl;};template <typename Z, typename... N>  // N是 模板参数包,(一包类型)void func(Z firstArg, N... OtherArgs) // firstArg 是解开的第一个参数,OthreArgs 是函数参数包 ,(一包参数){cout << "收到的参数值:" << firstArg << endl;func(OtherArgs...); // 递归调用  }
int main()
{func("test", 34, 3.4,"func");
}
上例中的递归调用过程是这样的:
func("test", 34, 3.4,"func");
func( 34, 3.4,"func");
func( 3.4,"func");
func("func");
收到的参数值:test
收到的参数值:34
收到的参数值:3.4
收到的参数值:func
递归终止函数被调用
  • 每一次递归都提取出参数包里的第一个参数,直到这个参数包里只剩一个参数时,因为事先重载了同名函数,所以最后一次调用的是普通函数,即递归终止函数。

    • 因为重载的是无参的同名函数,所以程序中允许这样的调用
    int main()
    {func()
    }
    
    • 如果重载的是单参数的同名函数, 程序如下

      template <typename Z>void func(Z args) // 当 OtherArgs为一个参数 时, 调用这个函数{cout << "剩余一个参数时候被打印" << args << endl;};template <typename Z, typename... N>  // N是 模板参数包,(一包类型)void func(Z firstArg, N... OtherArgs) // firstArg 是解开的第一个参数,OthreArgs 是函数参数包 ,(一包参数){cout << "收到的参数值:" << firstArg << endl;func(OtherArgs...); // 递归调用}int main(){func("1", 2, 3, 4,  "5", 6, "7");}
      

      输出结果:

      收到的参数值:1
      收到的参数值:2
      收到的参数值:3
      收到的参数值:4
      收到的参数值:5
      收到的参数值:6
      剩余一个参数时候被打印7
      
      • 但这样就不能调用无参数的函数了
       int main()
      {
      func();//error : [Error] no matching function for call to 'func0'
      }
      
    • 如果重载的是单参数和两个参数的同名函数, 程序如下

     template <typename Z>void func(Z args) // 当 OtherArgs为一个参数 时, 调用这个函数{cout << "剩余一个参数时候被打印" << args << endl;};template <typename Z, typename N>void func(Z args, N args2) // 这个函数存在的时候,不会调用上面一个参数的(因为使用上面的,还会有存在一个参数包)。当两个参数模板都适用某种情况时,优先使用没有“template parameter pack”的版本。{cout << "剩余两个参数的时候被打印"<< "  args  " << args << "  args2  " << args2 << endl;};template <typename Z, typename... N>  // N是 模板参数包,(一包类型)void func(Z firstArg, N... OtherArgs) // firstArg 是解开的第一个参数,OthreArgs 是函数参数包 ,(一包参数){cout << "收到的参数值:" << firstArg << endl;func(OtherArgs...); // 递归调用}int main(){func("1", 2, 3, 4,  "5", 6, "7");}
    收到的参数值:1
    收到的参数值:2
    收到的参数值:3
    收到的参数值:4
    收到的参数值:5
    剩余两个参数的时候被打印  args  6  args2  7
    

    可见没有调用 void func(Z args) 而是调用 void func(Z args, N args2)

    • 因为重载了单参函数,所以可以调用单参数的函数了

      • int main()
        {
        func("ers");
        }
        

采取逗号表达式
namespace detail4
{template <class T>  //递归终止函数int print(T t){cout << t << endl;return 0;}template <class... Args>void expand(Args... args){int arr[] = {(print(args), 0)...}; // 核心就是它}
int main()
{expand(1,2,3,4,5,6);cout<<"main end!!"<<endl;
}

逗号表达式会按顺序执行逗号前面的表达式,比如:

d = (a = b, c); 

这个表达式会按顺序执行:b会先赋值给a,接着括号中的逗号表达式返回c的值,因此d将等于c。

expand函数中的逗号表达式:(printarg(args), 0) , 也是按照这个执行顺序,先执行printarg(args) ,再得到逗号表达式(printarg(args), 0)的结果0。

{(print(args), 0)...} 第一步先展开print的形参参数包:

(print(args1), 0) (print(args2), 0) (print(args3), 0)(print(argsN), 0)

第二步 执行逗号表达式:

(printarg(args), 0)的结果0。 所以初始化列表为 {0,0,0,0...}

  • 很明显可以看到, 我们 是在 第一步把 参数展开的,那为什么需要逗号表达式呢???

为什么需要逗号表达式

现在假设我们去掉逗号表达式,只使用第一步 展开print的形参参数包

auto arr = {(print(args)...)} 将被展开为 {print(args1),print(args2)print(args3)}

这里初始化列表的作用 是将展开的多个参数 聚合起来.可以使用初始化列表接受任意长度的参数

 auto arr = {(print(args)...)};报错::'std::initializer_list<auto> arr' has incomplete type  
  • 不完整类型是这样一种类型,它缺乏足够的信息例如长度去描述一个完整的对象
  • 不完整类型必须通过某种方式补充完整,才能使用它们进行实例化,否则只能用于定义指针或引用

而逗号表达式 把这个表达式 print(args1) print(args2) print(args3) 的值 统一成整数0了, 也就是 (print(args), 0) 的返回值是0,这样初始化列表就像这样 auto arr = {0,0,0,0}.

既然是这样 我们只要让 print(args1) print(args2) print(args3) … 返回一个完整类型的值即可, 而且是要在编译期间就能返回确定的值

  • 我们可以借助constexpr () 函数,在编译期就返回值
#include<iostream>
using namespace std;
namespace detail4
{template <class T>  //递归终止函数
constexpr	int print(T t){cout << t << endl;return 0;}template <class... Args>void expand(Args... args){
//		auto arr = {(print(args), 0)...}; // 核心就是它auto arr ={print(args)...}; //这个参数在编译期就能确定}
}int main(){detail4::expand(1,2,3,4,5,6);cout<<"main end!!"<<endl;}

运行结果:

1
2
3
4
5
6
main end!!
  • 虽说constexpr函数所定义的是编译期的函数,但实际上在运行期constexpr函数也能被调用。事实上,如果使用编译期常量参数调用constexpr函数,我们就能够在编译期得到运算结果;而如果使用运行期变量参数调用constexpr函数,那么在运行期我们同样也能得到运算结果。
其他方式
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)...};}();
} 

应用

1.求最大值(可接受多个参数)

//1.求最大值(可接受多个参数)int maximum(int n)   //递归终止函数
{return n;
}template<typename... Args>
int maximum(int n, Args... args) //递归函数
{return std::max(n, maximum(args...)); 
}

2. 用可变参数模板函数模仿printf的功能

//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 argumentreturn;}cout << *s++; //"%d %s %p %f\n"输出非格式控制字符,如空格。            }throw runtime_error("extra arguments provided to print");
}

3. 使用tuple转化并分解参数包

//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
}

获取可变参函数模板中的值

参考折叠表达式



可变参类模板

  • 允许模板定义中包含0到多个(任意个)模板参数

基本外观和介绍

这个使用 递归+继承的方式 实现的版本 介绍.

三段式 :

#include <iostream>
using namespace std;
// 主模板   <3>
template <typename... Args>
class Test
{
};/*    主模板也可以仅仅保留声明   // <3.1>template <typename... Args>class Test;
*/// 特化模板中展开 <4>
template <typename _Ty, typename... Args>
class Test<_Ty, Args...> : private Test<Args...> //<1> 继承父类
{
public:Test(){};Test(_Ty data, Args... args) : _data(data), Test<Args...>(args...){printf("偏特化版本执行了,this = %p,sizeof...(Others)=%d\n", this, sizeof...(args));}; //<2> 调用父类来处理args 参数
private:_Ty _data;
};int main(int argc, const char **argv)
{Test<int, float, string> one(2, 2.5f, "helloWorld");  // <6>Test<> two(); //  <7>
}
  • <1>

    • Test<_Ty, Args...> 继承自 Test<Args...> 代入参数 就是Test <int,double,string> 继承自 Test<double,string>
  • <2>

    • Test(_Ty data, Args... args) : _data(data), Test<Args...>(args...){}; 调用父类构造函数,
  • <3>

    • 主模板template <typename... Args>就是 泛化的类模板, 主模板是不需要实例化形参的; 有人也会把 主模板仅仅保留声明<3.1>. 主模板的存在,是为了让特化模板存在.

这里的主模板的参数接收范围是 0到多个 因为 ...Args 的范围就是0到多个,

  • <4>

    • 特化的模板是用来 参数展开的 . 特化模板的参数接收范围是 1到多个 ,<typename _Ty, typename... Args> 至少有传入一个参数才会调用它.
  • <7>

  • 执行 <7>Test<> two(); 将是不会调用任何模板的, 解决办法是还要添加一个无参的特化模板. 这个无参的特化模板也可以当成是终止递归的函数(这个后面会讲).

template <>   //调用0个参数
class Test<>
{
public:Test(){cout<<"调用无参数的"<<endl;}
};

上面的三段式模板参数是可以传入0个参数的. 因为可变参数模板中的模板参数可以有0个,但有时候0个模板参数没有意义,此时就可以选择下面的两段式.

两段式:


// 在主模板中展开 参数
template <typename _Ty, typename... OtherArgs>   // 主模板
class Test : private Test<OtherArgs...> //<1> 继承父类
{
public:Test(){};Test(_Ty FirstData, OtherArgs... args) : _data(FirstData), Test<OtherArgs...>(args...){printf("偏特化版本执行了,this = %p,sizeof...(Others)=%d\n", this, sizeof...(args));}; //<2> 调用父类来处理args 参数
private:_Ty _data;
};template<typename _Ty>
class Test<_Ty>
{
public:Test(_Ty data):_data(data){};private:_Ty _data;
};int main()
{Test<string> three("string");//Test<>test(); // error ,因为主模板接收 1到多个参数,所以不会接收 0个参数的类模板
//而且正因为主模板是接收 1到多个参数, 所以这里如果 写了 接收 0个参数的特化类模板将会报错
//[Error] wrong number of template arguments (O, should be at least 1)
//[错误]错误的模板参数的数量(O,应该是至少1)cout<<"main endl"<<endl;
}
  • 和前面的三段式相比;他们的主模板是不同的;

    • 三段式是template <typename... Args>class Test 而 两段式是

      template <typename _Ty, typename... OtherArgs> 他们的可接受参数是不同的, 三段式接收 0到 多个参数

    而两段式 接收 1到多个 不能是接收 0个参数

    • 所以这里如果 写了 接收 0个参数的特化类模板将会报错[Error] wrong number of template arguments (0, should be at least 1)
      错误的模板参数的数量(0,应该是至少1)

    •  template <>   class Test<>{public:Test(){cout<<"调用无参数的"<<endl;}};
      
  • 其次 类模板参数展开的位置不同 ;

    • 三段式是 在 特化的模板类template <typename _Ty, typename... Args> class Test<_Ty, Args...>中展开的

    而两段式是在 主模板template <typename _Ty, typename... OtherArgs> class Test 中展开的

  • 递归结束函数的写法不同

    • 三段式的递归结束函数 可以是无参的 这样 模板参数是可以传入0个参数的.
    • 三段式的递归结束函数, 不包含单参数的特化 函数,也能传入单个参数
    #include <iostream>
    using namespace std;
    // 主模板   <3>
    template <typename... OtherArgs>   //...参数包里面的个数就是 0到多个,所以这个是会接收0 个参数的
    class Test
    {};// 特化模板中展开 <4>
    template <typename _Ty, typename... OtherArgs>
    class Test<_Ty, OtherArgs...> : public Test<OtherArgs...> //<1> 继承父类
    {
    public:Test(){};Test(_Ty FirstData, OtherArgs... args) : _data(FirstData), Test<OtherArgs...>(args...){printf("偏特化版本执行了,this = %p,sizeof...(Others)=%d\n", this, sizeof...(args));}; //<2> 调用父类来处理args 参数private:_Ty _data;	};// 特化一个接收无参的类
    template <>   
    class Test<>
    {
    public:Test(){cout<<"调用无参数的"<<endl;}
    };int main()
    {
    Test<> test1; //三段式的递归结束函数 可以是无参的  这样 模板参数是可以传入0个参数的.Test<int> test2(100); //三段式的递归结束函数, 不包含单参数的特化 函数,也能传入单个参数
    }
    • 三段式的递归结束函数 也可以是单参数, 多参数的.
    #include <iostream>
    using namespace std;
    // 主模板   <3>
    template <typename... OtherArgs>   //...参数包里面的个数就是 0到多个,所以这个是会接收0 个参数的
    class Test
    {};// 特化模板中展开 <4>
    template <typename _Ty, typename... OtherArgs>
    class Test<_Ty, OtherArgs...> : public Test<OtherArgs...> //<1> 继承父类
    {
    public:Test(){};Test(_Ty FirstData, OtherArgs... args) : _data(FirstData), Test<OtherArgs...>(args...){printf("偏特化版本执行了,this = %p,sizeof...(Others)=%d\n", this, sizeof...(args));}; //<2> 调用父类来处理args 参数private:_Ty _data;};// 特化一个接收无参的类
    template <>   
    class Test<>
    {
    public:Test(){cout<<"调用无参数的"<<endl;}
    };// 特化一个接受单参的类
    template <typename _Ty>   
    class Test<_Ty>
    {
    public:Test(_Ty data):m_data(data){cout<<"调用含有一个参数的"<<endl;	};private:_Ty m_data;
    };
    // 特化一个接受双参的类
    template <typename _Ty,typename _Se>   
    class Test<_Ty,_Se>
    {
    public:Test(_Ty data,_Se data2):m_data(data),m_data2(data2){cout<<"调用含有双参数的"<<endl;	};private:_Ty m_data;_Se m_data2;
    };int main()
    {
    // 调用无参数的
    Test<> test1; //调用单参数的
    Test<int> test2 (10); //调用双参数的
    Test<int, string> test3 (10,"helloWorld"); //调用多参数Test <int,string,double,float> (100,"string",100.00,1.2f);}
    
    • 而两段式必须包含单参数的递归结束函数,否则将不能 模板参数传入单个参数.
    • 两段式 模板参数不可以传入0个参数的.
    
    #include <iostream>
    using namespace std;// 主模板展开 <4>
    template <typename _Ty, typename... OtherArgs>   // 主模板
    class Test : private Test<OtherArgs...> //<1> 继承父类
    {
    public:Test(){};Test(_Ty FirstData, OtherArgs... args) : _data(FirstData), Test<OtherArgs...>(args...){printf("偏特化版本执行了,this = %p,sizeof...(Others)=%d\n", this, sizeof...(args));}; //<2> 调用父类来处理args 参数
    private:_Ty _data;
    };template<typename _Ty,typename _Last>
    class Test<_Ty, _Last>
    {
    public:Test(_Ty data, _Last data2):m_data(data),m_data2(data2){cout<<"调用了两个参数的"<<endl;};private:_Ty m_data;_Last m_data2;};
    /*    单参数的特化模板被注释掉, 就不能再传入单个参数的模板参数
    template<typename _Ty>
    class Test<_Ty>
    {
    public:Test(_Ty data):m_data(data){cout<<"调用单参数的"<<endl;};
    private:_Ty m_data;};
    */
    int main()
    {Test<> test; //  error两段式  模板参数不可以传入0个参数的.   Test<int> test2(100); // error  而两段式必须包含单参数的递归结束函数,否则将不能 模板参数传入单个参数.
    }
    • 两段式 不能特化 无参的模板类 (因为主模板最少只能接收一个参数)
    
    #include <iostream>
    using namespace std;// 主模板展开 <4>
    template <typename _Ty, typename... OtherArgs>   // 主模板
    class Test : private Test<OtherArgs...> //<1> 继承父类
    {
    public:Test(){};Test(_Ty FirstData, OtherArgs... args) : _data(FirstData), Test<OtherArgs...>(args...){printf("偏特化版本执行了,this = %p,sizeof...(Others)=%d\n", this, sizeof...(args));}; //<2> 调用父类来处理args 参数
    private:_Ty _data;
    };template<typename _Ty,typename _Last>
    class Test<_Ty, _Last>
    {
    public:Test(_Ty data, _Last data2):m_data(data),m_data2(data2){cout<<"调用了两个参数的"<<endl;};private:_Ty m_data;_Last m_data2;};
    template<typename _Ty>
    class Test<_Ty>
    {
    public:Test(_Ty data):m_data(data){cout<<"调用单参数的"<<endl;};
    private:_Ty m_data;};
    template<>
    class Test<>
    {
    public:Test(){cout<<"调用无参数的"<<endl;};};
    int main()
    {//	Test<> test; //  不能传入无参}

执行结果和原理分析

  • 到目前为止,我们并未进行可变参类模板的执行结果进行分析. 现在让我们来分析 (这里我们选择两段式来分析,其实都是相同的)
#include <iostream>
using namespace std;// 主模板展开 <4>
template <typename _Ty, typename... OtherArgs>   // 主模板
class Test : private Test<OtherArgs...> //<1> 继承父类
{
public:Test(){};Test(_Ty FirstData, OtherArgs... args) : _data(FirstData), Test<OtherArgs...>(args...){printf("偏特化版本执行了,this = %p,sizeof...(Others)=%d\n", this, sizeof...(args));}; //<2> 调用父类来处理args 参数
private:_Ty _data;
};template<typename _Ty>
class Test<_Ty>
{
public:Test(_Ty data):m_data(data){cout<<"调用单参数的"<<endl;};
private:_Ty m_data;	
};int main()
{Test<int,float,string>(100,2.5f,"helloWorld");}

执行结果:

调用单参数的偏特化版本执行了,this = 0000004f249ff800,sizeof...(Others)=1偏特化版本执行了,this = 0000004f249ff800,sizeof...(Others)=2
  • 可见 是单参数的 构造函数 先执行,再是双参数,最后是三个参数的被调用

这里贴一张图:

image-20230204204747457

image-20230204204826613

如何获取参数包中的值

在之前的基础上 提供接口 之后的代码

// todo  可变参数类模板参数包的展开
// 继承+模板特化
#include <iostream>
using namespace std;
// 主模板
template <typename... Args>
class Test
{
};// 特化模板中展开
template <typename _Ty, typename... Args>
class Test<_Ty, Args...> : private Test<Args...> // 继承父类
{
public:Test(){};Test(_Ty data, Args... args) : _data(data), Test<Args...>(args...){printf("偏特化版本执行了,this = %p,sizeof...(Others)=%d\n", this, sizeof...(args));}; // 调用父类来处理args 参数// 类外访问数据提供接口_Ty &head() { return _data; }; // 返回参数包中第一个参数Test<_Ty, Args...> &tail1() { return *this; }; // 返回本类对象.()  通过类对象能再次调用head(调用当前的第一个元素)Test<Args...> &tail2() { return *this; };      // 返回父类对象.  通过类对象能再次调用head(不断获取剩下参数包的第一个元素)private:_Ty _data;
};int main(int argc, const char **argv)
{Test<int, float, string> one(2, 2.5f, "helloWorld");// 访问第一个数据cout << "firstArg:: " << one.head() << endl;cout << "firstArg2:: " << one.tail1().head() << endl;// 访问第二个数据cout << "secondArg:: " << one.tail2().head() << endl;cout << "secondArg:: " << one.tail2().tail1().head() << endl;// 访问第三个数据cout << "ThirdArg:: " << one.tail2().tail2().head() << endl;
}

结合上面两张图 , 我想大家 唯一有疑惑的地方可能就是下面这个

Test<Args...> &tail2() { return *this; };      // 返回父类对象.

其实这里是父类指针(引用) 指向子类对象.

比如:

#include<iostream>
using namespace std;
class F 
{
public:int f=100;
};
class S:public F
{
public:int s=200;F Fr;F&retF(){return *this; //这里也是父类引用指向子类对象}
};int main()
{S s;F *Fr =&s;     //父类指针指向子类对象cout<<Fr->f<<endl; //父类指针可以引用到父类成员cout<<s.retF().f;  //这种方式也能调用到父类的成员. 且 s.retF() 返回的是父类地址
}

所以 子类对象(one).tail2() 其实 返回的是 父类的地址

组合+递归形式 实现

之前的是用递归+继承的方式实现的, 现在介绍一下组合+递归的方式


#include <iostream>
using namespace std;
using std::string;
// todo 使用递归方式展开
// 主模板
template <typename... Args>
class Tup
{
};// 在特化版本中展开template <typename _Ty, class... Args>
class Tup<_Ty, Args...>
{
public:Tup() = default;Tup(_Ty Firstdata, Args... OtherArgs) : data(Firstdata), args(OtherArgs...){printf("偏特化版本执行了,this = %p,sizeof...(Others)=%d\n", this, sizeof...(args));}; // args 是Tup<Args...> 类型的,所以这里是在递归调用_Ty &head() { return data; }; // 返回参数包第一个数据Tup<Args...> &tail2() // 返回下一个的递归深度的 类对象{return args;// return *this; // 由于不是继承的关系(不存在父类指针(引用)指向子类对象),这里没法用this 返回下一个递归深度的对象,报错:返回临时引用};Tup<_Ty, Args...> &tail1() // 返回这个递归深度的 类对象{// return args; 地址都是一样的return *this; // 返回本类的对象是可以的}protected:_Ty data;Tup<Args...> args; // 下一个递归深度的类对象
};template <>
class Tup<> // 递归终止函数.  //三段式必须含有空的特化类模板
{
public:Tup(){cout << "递归终止函数被调用" << endl;}
};
template <typename _Ty>
class Tup<_Ty> // 含有一个参数的 递归终止函数.
{
public:Tup(_Ty Firstdata) : data(Firstdata){cout << "含有一个参数的递归终止函数被调用" << endl;}; // args 是Tup<Args...> 类型的,所以这里是在递归调用
private:_Ty data;public:_Ty &head() { return data; };
};
void testArgsFirst()
{Tup<int, float, string> one(2, 2.5f, "helloWorld");// 访问第一个数据cout << "firstArg:: " << one.head() << endl;cout << "firstArg2:: " << one.tail1().head() << endl;// 访问第二个数据cout << "secondArg:: " << one.tail2().head() << endl;cout << "secondArg2:: " << one.tail2().tail1().head() << endl;// 访问第三个数据cout << "ThirdArg:: " << one.tail2().tail2().head() << endl;
}int main()
{testArgsFirst();
}

这里贴一张图.

image-20230204215223588

  • 这个很好理解就是简单的递归


泛型的应用

4可变参数模版实现泛化的delegate

C++中没有类似C#的委托,我们可以借助可变模版参数来实现一个。C#中的委托的基本用法是这样的:

delegate int AggregateDelegate(int x, int y);//声明委托类型int Add(int x, int y){return x+y;}
int Sub(int x, int y){return x-y;}AggregateDelegate add = Add;
add(1,2);//调用委托对象求和
AggregateDelegate sub = Sub;
sub(2,1);// 调用委托对象相减
#include<iostream>
#include<string>
using namespace std;
using  std::string;
template <class T, class R, typename... Args>
class  MyDelegate
{
public:MyDelegate(T* t, R  (T::*f)(Args...) ):m_t(t),m_f(f) {}//	对() 进行重载, MyDelegate() 就能调用这个函数,函数的返回值就是RR operator()(Args&&... args)  //Args 是类型包{cout<<"传入T的类型 "<<typeid(T).name()<<endl;cout<<"传入R的类型 "<<typeid(R).name()<<endl;
//		return (m_t->*m_f)(std::forward<Args>(args) ...);//完美转发return (m_t->*m_f)(args...); //m_t是类对象,m_f 是函数指针,(args...)就是将函数的参数逐个展开//这句话就是调用函数  , (函数名)(函数参数)}private: T* m_t;                  //T 是对象类型R  (T::*m_f)(Args...);  // R是函数的返回类型 ,就是定义了一个函数指针, void(*pf)(int,int)
};   template <class T, class R, typename... Args>
MyDelegate<T, R, Args...> CreateDelegate(T* t, R (T::*f)(Args...)) //参数:接收类对象(用类对象的指针),接收一个可变参函数(用可变参函数的指针)
{																			return MyDelegate<T, R, Args...>(t, f);  //创建Mydelegate对象.(将类对象,函数指针传入),
}struct A
{void Fun(int i){cout<<i<<endl;}void Fun1(int i, double j){cout<<i+j<<endl;}int Fun2(int a,double b,string c){cout<<a<<b<<c<<endl;return 999;}
};int main()
{A a;auto d = CreateDelegate(&a, &A::Fun); //创建委托  返回MyDelegate 对象(传入了类对象a,和类中的函数(函数又包含了函数返回类型R,函数名f,函数参数Args))d(1); //调用委托,将输出1   .  d 是	MyDelegate 类型;  对象() 调用了括号运算符 auto d1 = CreateDelegate(&a, &A::Fun1); //创建委托
d1(1, 2.5); //调用委托,将输出3.5
//	cout<<"d().type:: "<<typeid(decltype(d(1))).name()<<endl;  //返回值类型是R,也就是Fun函数返回类型auto d2 = CreateDelegate(&a,&A::Fun2);
cout<<"d2.RetType:: "<<typeid(decltype(d2(1,1.1,"hello"))).name()<<endl;  //返回值是int
}

s&&… args) //Args 是类型包
{

	cout<<"传入T的类型 "<<typeid(T).name()<<endl;cout<<"传入R的类型 "<<typeid(R).name()<<endl;

// return (m_t->*m_f)(std::forward(args) …);//完美转发
return (m_t->*m_f)(args…); //m_t是类对象,m_f 是函数指针,(args…)就是将函数的参数逐个展开
//这句话就是调用函数 , (函数名)(函数参数)

}

private:
T* m_t; //T 是对象类型
R (T::*m_f)(Args…); // R是函数的返回类型 ,就是定义了一个函数指针, void(*pf)(int,int)
};

template <class T, class R, typename… Args>
MyDelegate<T, R, Args…> CreateDelegate(T* t, R (T::*f)(Args…)) //参数:接收类对象(用类对象的指针),接收一个可变参函数(用可变参函数的指针)
{
return MyDelegate<T, R, Args…>(t, f); //创建Mydelegate对象.(将类对象,函数指针传入),
}

struct A
{
void Fun(int i){cout<<i<<endl;}
void Fun1(int i, double j){cout<<i+j<<endl;}
int Fun2(int a,double b,string c){
cout<<a<<b<<c<<endl;
return 999;
}
};

int main()
{
A a;

auto d = CreateDelegate(&a, &A::Fun); //创建委托  返回MyDelegate 对象(传入了类对象a,和类中的函数(函数又包含了函数返回类型R,函数名f,函数参数Args))
d(1); //调用委托,将输出1   .  d 是	MyDelegate 类型;  对象() 调用了括号运算符 
auto d1 = CreateDelegate(&a, &A::Fun1); //创建委托

d1(1, 2.5); //调用委托,将输出3.5
// cout<<"d().type:: "<<typeid(decltype(d(1))).name()<<endl; //返回值类型是R,也就是Fun函数返回类型

auto d2 = CreateDelegate(&a,&A::Fun2);
cout<<"d2.RetType:: "<<typeid(decltype(d2(1,1.1,“hello”))).name()<<endl; //返回值是int
}



http://www.ppmy.cn/news/22440.html

相关文章

Vue理解

1.是什么&#xff1f;Vue是一个用于创建用户界面的开源JavaScript框架&#xff0c;也是一个创建单页应用的web应用框架。Vue所关注的核心是MVC模式中的视图层&#xff0c;同时它也能方便地获取数据更新&#xff0c;并通过组件内部特定的方法实现视图与模型的交互。2.Vue核心特性…

Pyspark基础入门3

Pyspark 注&#xff1a;大家觉得博客好的话&#xff0c;别忘了点赞收藏呀&#xff0c;本人每周都会更新关于人工智能和大数据相关的内容&#xff0c;内容多为原创&#xff0c;Python Java Scala SQL 代码&#xff0c;CV NLP 推荐系统等&#xff0c;Spark Flink Kafka Hbase Hi…

C++高级教程——C++多线程

C高级教程——C多线程C 多线程创建线程终止线程实例连接和分离线程C 多线程 多线程是多任务处理的一种特殊形式&#xff0c;多任务处理允许让电脑同时运行两个或两个以上的程序。在一般情况下&#xff0c;有两种类型的多任务处理&#xff1a;基于进程和基于线程。 基于进程的…

Idea 运行多个微服务 Idea 一个服务开启多个端口运行 idea 开启多个端口服务

Idea 运行多个微服务 Idea 一个服务开启多个端口运行 idea 开启多个端口服务 一、情景描述 在使用idea 练习微服务项目时&#xff0c;如 要测试负载均衡功能&#xff0c;那么一个项目需要开启多个不同端口的服务&#xff0c;这个在idea中 如何实现呢&#xff1f; 二、设置步骤 …

Linux系统的服务管理

1、服务的概念 操作系统中在后台持续运行的程序&#xff0c;本身并没有操作界面&#xff0c;需要通过端口号访问和操作。CentOS 6和CentOS 7的服务管理有很大区别&#xff0c;我们分别来看。 2、CentOS6服务 ①service命令 启动服务&#xff1a;service 服务名 start 停止…

自动化测试成长路上必会技巧

目录 1. 首先要搞明白几个问题 2. 语言基础 3. selenium学习&#xff0c;脚本编写 4. 执行测试&#xff0c;脱离脚本阶段 5. 参数化、配置、日志、简单框架搭建 6. 自动执行&#xff0c;自动发送报告 7. 高级货&#xff0c;平台&#xff1f;分布式&#xff1f;界面&…

Hadoop 复习 ---- chapter08

Hadoop 复习 ---- chapter08RPC 定义&#xff1a; Remote Procedure Call&#xff0c;远程方法调用&#xff0c;它允许一台计算机程序远程调用另外一台计算机的子程序&#xff0c;而不关心底层的网络通信细节。RPC 经常用于分布式网络通信中。 RPC 特点&#xff1a;透明性、高…

OSG三维渲染引擎编程学习之三十四:“第三章:OSG场景组织” 之 “3.16 ClipNode剪切面节点”

目录 第三章:OSG场景组织 3.16 ClipNode剪切面节点 3.16.1 ClipNode介绍 3.16.2 ClipNode示例 第三章:OSG场景组织 在OSG中存在两个树:场景树、渲染树。其中,场景树是由一系列节点Node组成,这些节点Node可以是矩阵变换、状态变换,