这才是C++泛型之美

前言

上一章节主要是详细介绍了C++泛型编程基础,不清楚的能够回顾一下哦。本章节主要针对于C++STL(标准模板类库)作个详细介绍。C++的新特性--可变模版参数(variadic templates)是C++新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而因为可变模版参数比较抽象,使用起来须要必定的技巧,因此它也是C++中最难理解和掌握的特性之一。虽然掌握可变模版参数有必定难度,可是它倒是C++中最有意思的一个特性,本文但愿带领读者由浅入深的认识和掌握这一特性,同时也会经过一些实例来展现可变参数模版的一些用法。html

初识可变模版参数

可变参数模板和普通模板的语义是同样的,只是写法上稍有区别,声明可变参数模板时须要在typenameclass后面带上省略号“...”。好比咱们经常这样声明一个可变模版参数:template<typename...>或者template<class...>,一个典型的可变模版参数的定义是这样的:ios

template <class... T> void f(T... args);

省略号的做用:c++

  • 声明一个参数包T... args,参数包中可包含0到n个模板参数;编程

  • 在模板定义的右边,能够将参数包展开成一个一个独立的参数。数组

省略号的参数称为参数包,它里面包含了0NN>=0)个模版参数。咱们没法直接获取参数包args的每一个参数的,只能经过展开参数包的方式来获取参数包中的每一个参数,这是使用可变模版参数的一个主要特色,也是最大的难点,即如何展开可变模版参数。可变模板参数分类:微信

  • 可变模版参数函数app

  • 可变模版参数类编辑器

可变模板参数函数

1

打印可变模版参数函数的参数个数


#include <iostream>#include <string>using namespace std;template <class ...Type>void print(Type ...data) { cout << sizeof...(data) << endl;}int main() { print(); print(1); print(1, "ILoveyou"); print(1, 2, 3.4, "IMissyou"); return 0;}

上面的例子中,print()没有传入参数,因此参数包为空,输出的size为0,后面两次调用分别传入两个和三个参数,故输出的size分别为2和3。因为可变模版参数的类型和个数是不固定的,因此咱们能够传任意类型和个数的参数给函数print。这个例子只是简单的将可变模版参数的个数打印出来,若是咱们须要将参数包中的每一个参数打印出来的话就须要经过一些方法了。展开可变模版参数函数的方法通常有两种:ide

1.经过递归函数来展开参数包。函数

2.逗号表达式来展开参数包。

2
经过递归函数来展开参数包

经过递归函数展开参数包,须要提供一个参数包展开的函数和一个递归终止函数,递归终止函数正是用来终止递归的,以下面的例子:

#include <iostream>using namespace std;//递归终止函数void print(){ cout << "递归终止函数" << endl;}//展开函数template <class T,class ...Type>void print(T data,Type...exData){ cout << data << endl; print(exData...);}int main(){ print(1, 2, 3, 4); return 0;}

上例会输出每个参数,直到为空时输出"递归终止函数"。展开参数包的函数有两个,一个是递归函数,另一个是递归终止函数,参数包exData...在展开的过程当中递归调用本身,每调用一次参数包中的参数就会少一个,直到全部的参数都展开为止,当没有参数时,则调用非模板函数print终止递归过程。固然上述终止函数也能够写成带参数函数模板:

接下来用模板函数做为终止函数写一个不限参求和函数,具体实现代码以下:

#include <iostream>
using namespace std;
//递归终止函数
template <typename Type>
Type sum(Type t)
{
   return t;
}
//展开函数
template <class T,class ...Type>
T sum(T a, Type ...b)
{
   return a + sum<T>(b...);
}
int main()
{
   cout << sum(1, 2, 3, 4) << endl;
   cout << sum(1, 2, 3) << endl;
   return 0;
}

sum在展开参数包的过程当中将各个参数相加求和,参数的展开方式和前面的打印参数包的方式是同样的。

3

逗号表达式展开参数包


递归函数展开参数包是一种标准作法,也比较好理解,但也有一个缺点,就是必需要一个重载的递归终止函数,即必需要有一个同名的终止函数来终止递归,这样可能会感受稍有不便。有没有一种更简单的方式呢?其实还有一种方法能够不经过递归方式来展开参数包,这种方式须要借助逗号表达式和初始化列表。好比前面打印函数能够改为这样:

#include <iostream>
using namespace std;
//递归终止函数
template <class T>
void print(T data)
{
   cout << data << "\t";
}
template <class ...Type>
void print(Type ...exData)
{
   int array[] = { (print(exData),0)... };
}

int main()
{
   print(1, 2, 3);
   cout << endl;
   print("张三", 1, 3);
   return 0;
}

这个数组的目的纯粹是为了在数组构造的过程展开参数包。咱们能够把上面的例子再进一步改进一下,将函数做为参数,就能够支持lambda表达式了,从而能够少写一个递归终止函数了,具体代码以下:

template<class Fclass... Args>void expand(const FfArgs&&...args{  initializer_list<int>{(f(std::forward< Args>(args)),0)...};}expand([](int i){cout<<i<<endl;}, 1,2,3);


可变模版参数类

1
库中的可变模板参数类

std::tuple就是一个可变模板类

template< class... Types >
class tuple;

这个可变参数模板类能够携带任意类型任意个数的模板参数:

tuple<int> tp1 = std::make_tuple(1);
tuple<int, double> tp2 = std::make_tuple(1, 2.5);
tuple<int, double, string> tp3 = std::make_tuple(1, 2.5, “”);

可变参数模板的模板参数个数能够为0个,因此下面的定义也是合法的:

tuple<> tp;

可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不一样,可变参数模板类的参数包展开须要经过模板特化和继承方式去展开,展开方式比可变参数模板函数要复杂。

2

继承方式展开参数包


//整型序列的定义template<int...>struct IndexSeq{};
//继承方式,开始展开参数包template<int N, int... Indexes>struct MakeIndexes : MakeIndexes<N - 1, N - 1, Indexes...> {};
// 模板特化,终止展开参数包的条件template<int... Indexes>struct MakeIndexes<0, Indexes...>{ typedef IndexSeq<Indexes...> type;};
int main(){ using T = MakeIndexes<3>::type; cout <<typeid(T).name() << endl; return 0;}

可变模板做用

1

变参数模版消除重复代码


C++11以前若是要写一个泛化的工厂函数,这个工厂函数能接受任意类型的入参,而且参数个数要能知足大部分的应用需求的话,咱们不得不定义不少重复的模版定义,好比下面的代码:

#include <iostream>
using namespace std;
template<typename T, typename...  Args>
T* Instance(Args... args)
{
   return new T(args...);
}
class A
{
public:
   A(int a) :a(a) {}
   A(int a, int b) :a(a) {}
   A(int a, int b,string c) :a(a) {}
   void print()
  {
       cout << a << endl;
  }
   int a;
};
class B
{
public:
   B(int a, int b) :a(a), b(b) {}
   void print()
  {
       cout << a << endl;
       cout << b << endl;
  }
   int a;
   int b;
};
int main()
{
   A* pa = Instance<A>(1);
   B* pb = Instance<B>(1, 2);
   pa->print();
   pb->print();
   pa = Instance<A>(100, 2);
   pa->print();
   pa = Instance<A>(100, 2,"Loveyo");
   pa->print();
   return 0;
}
2

变参数模版"万能函数"

万能函数相似C#中的委托功能,具体实现以下:

template <class T, class R, typename... Args>class MyDelegate{public: MyDelegate(T* t, R (T::*f)(Args...) ):m_t(t),m_f(f) {} R operator()(Args... args){ return (m_t->*m_f)(args ...); } //R operator()(Args&&... args)  //{ //return (m_t->*m_f)(std::forward<Args>(args) ...); //}
private: T* m_t; R (T::*m_f)(Args...);};
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);}
struct A{ void Fun(int i){cout<<i<<endl;} void Fun1(int i, double j){cout<<i+j<<endl;}};
int main(){ A a; auto d = CreateDelegate(&a, &A::Fun); //建立委托 d(1); //调用委托,将输出1 auto d1 = CreateDelegate(&a, &A::Fun1); //建立委托 d1(1, 2.5); //调用委托,将输出3.5}


尾言

本栏到这里结束了,做业:本身谢谢可变参数模板。难度不大,重在重载。




本文分享自微信公众号 - C语言编程基础(goodStudyCode)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索