C++可变参模板

可变参数模板是一个接受可变数目参数的模板函数或模板类,在模板参数列表中,typename…指出接下来的参数表示0个或多个类型的列表,一个类型名后面跟省略号表示0个或多个给定类型的非类型参数的列表。在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。

可变参数模板通常是递归的,第一步调用处理包中的第一个实参,然后用剩余实参调用自身, sizeof…运算符可以获得包中元素数量。

一、可变参数模板函数

如果需要用参数包中的参数,则一定要将参数包展开。有两种展开参数包的方法:
(1)通过递归的模板函数来将参数包展开
(2)通过逗号表达式和初始化列表方式展开参数包

1、可变参数模板函数的定义

template <class... T>
void f(T... args)
{
    cout<<sizeof...(args)<<endl; //打印变参的个数
}
f();//0
f(1,2);//2
f(1,2.5,"");//3

代码

namespace A
{
	//T 理解成0到多个不同的类型,那对应的参数args也应该是多个不同类型的参数
	//参数包中可以容纳0 到多个模板参数,而且模板参数可以为任意的类型
	template<typename... T>
	void Func(T... args)	//args称为参数包
	{
		cout << sizeof...(args) << endl;	//打印可变参的数量
		cout << sizeof...(T) << endl;	//参数类型
	}

	template<typename T, typename... U>
	void Func2(const T& a, const U&...args)	//注意引用类型符的位置
	{
		cout << sizeof...(args) << endl;	//打印可变参的数量
	}
}

int main()
{
	A::Func();
	A::Func(10, 25);
	A::Func("a", "b", 25);

	A::Func2(10);
	A::Func2(10,"abc",25);
	return 0;
}

注意:

  1. T 理解成0到多个不同的类型,那对应的参数args也应该是多个不同类型的参数。
  2. 参数包中可以容纳0 到多个模板参数,而且模板参数可以为任意的类型。
  3. 在具体函数形参中,&的位置,出现在类型名的后面。

2、递归函数方式展开参数包

  • 提供一个参数包展开的函数和一个递归终止函数。
  • 重载的递归终止函数。
#include<iostream>
using namespace std;
//递归终止函数
void print() 
{
    cout<<"empty"<<endl;
}
//参数包展开函数
template<class T,class ...Args>
void print(T head, Args... rest)
{
    cout<<"parameter "<<head<<endl;
    print(rest...);
}
int main(void)
{
    print(1,2,3,4);
    return 0;
}

通过type_traits来展开并打印参数包 没写呢

3、逗号表达式和初始化列表方式展开参数包

template <class T>
void printarg(T t)
{
    cout<<t<<endl;
}

template <class ...Args>
void expand(Args... args)
{
    int arr[]={(printarg(args),0)...};
}
expand(1,2,3,4);

二、可变参数模板类

std::tuple就是一个可变模板类,template <class… Types> class tuple;
可变参数模板类的参数包展开的方式:

  • 通过模板特化
  • 通过继承方式

1、递归继承方式展开参数包

namespace B
{
	//通过递归继承方式展开参数包
	template<typename... args> class MyClass {};	//主模板

	template<>
	class MyClass<>
	{
	public:
		MyClass() {
			printf("不带参数的构造函数被执行:%p\n", this);
		}
	};

	template<typename First, typename... Others>
	class MyClass<First, Others...> : private MyClass<Others...>	//偏特化
	{
	public:
		MyClass() : _first(0)
		{
			printf("构造函数被执行:%p\n", this);
		}
		MyClass(First p, Others...q) :_first(p), MyClass<Others...>(q...)
		{
			cout << "_first = " << _first << endl;
		}

		First _first;
	};
}

int main()
{
	//B::MyClass<> v;
	B::MyClass<int, float, double> b(12,13.5,23);	//先执行不带参数的构造函数,在展开参数包
	return 0;
}

2、继承方式展开参数包

//整型序列的定义
template<int...>
struct IndexSeq{};

//继承方式,开始展开参数包
template<int N, int... Indexs>
struct MakeIndexes:MakeIndexes<N-1,N-1,Indexes...>{};

//模板特化,终止展开参数包的条件
template<int... Indexs>
struct MakeIndexes<0,Indexes...>
{
    typedef IndexSeq<Indexes...>type;
};

int main()
{
    using T = MakeIndexes<3>::type;
    cout<<typeid(T).name()<<endl;
    return 0;
}

3、递归组合方式展开参数包

namespace B
{
	//通过递归组合方式展开参数包
	template<typename... args> class MyClass {};	//主模板

	template<>
	class MyClass<>
	{
	public:
		MyClass() {
			printf("不带参数的构造函数被执行:%p\n", this);
		}
	};

	template<typename First, typename... Others>
	class MyClass<First, Others...> 
	{
	public:
		MyClass() : _first(0)
		{
			printf("构造函数被执行:%p\n", this);
		}
		MyClass(First p, Others...q) :_first(p), _o(q...)
		{
			cout << "_first = " << _first << endl;
		}

		First _first;
		MyClass<Others...> _o;	//组合关系(复合关系)
	};
}

int main()
{
	//B::MyClass<> v;
	B::MyClass<int, float, double> b(12,13.5,23);	//先执行不带参数的构造函数,在展开参数包
	return 0;
}

4、tuple和递归调用展开参数包

这种展开参数包的方式需要写类的特化版本。
实现思路: 计数器从0 开始,每处理一个参数,计数器就 +1 ,一直到把所有参数处理完,最后用模板偏特化,作为递归调用结束。

namespace B
{
	//count用于统计, 从 0开始,maxCount表示参数数量
	template<int count, int maxCount, typename... T>
	class Test
	{
	public:
		static void Func(const tuple<T...>& t)
		{
			cout << "value = " << get<count>(t) << endl;
			Test<count + 1, maxCount, T...>::Func(t);
		}
	};

	//需要一个特化版本,用于结束递归调用
	template<int maxCount, typename...T>
	class Test<maxCount, maxCount, T...>
	{
	public:
		static void Func(const tuple<T...>& t)
		{
			
		}
	};

	template<typename... T>
	void Func(const tuple<T...> &t)	//可变参数函数模板
	{
		Test<0, sizeof...(T), T...>::Func(t);
	}
}

int main()
{
	tuple<float, int, int> tuple(12.5f, 100, 52);	//元组:一堆各种东西的组合
	B::Func(tuple);
	return 0;
}

C++11参数包展开

三、模板模板参数

模板参数就是模板的参数,我们一般指定为T类型,实际上可以使用任何的名字,例如指定一个Foo的模板参数:

temlate<typename Foo>
Foo calc(const Foo& a, const Foo& b)
{
    return a+b;
}

而模板模板参数则是模板的参数又是一个模板,例如:

template<typename T, template<typename U> typename Container>
class XCls
{
    private:
        Container<T> c;
};

模板的第一个参数是T类型,第二个参数是一个Container,他是一个可以指定一个U类型的变量。

那么如何使用他呢?

template<typename T>
class test
{
    private:
        T t;
};

int main(void)
{
    XCls<std::string, test> mylst1;

    return 0;
}

我们可以定义一个模板类,然后将其如上方式传入就可以了。

但是如果传入一个容器呢?比如:list

XCls<string, list> mylst1;

如果编译就会报错。我们分析一波:

将string 和 list传入到类XCls中,然后就会定义一个list的c变量,这样看起来是可以的,因此我们使用list容器的时候就是list<一个类型>,但是这里为什么就不行呢?是因为list容器实质上是有第二参数的,虽然第二参数有默认的参数,正如我们平常使用的那样,只需要指定一个参数,但是在这里无法通过编译,因此,我们使用如下解决办法:

template<typename T>
using Lst = std::list<T, std::allocator<T>>;

XCls<std::string, Lst> mylst2;
// 编译时需要加上std=c++11

使用C++11的using关键字的新功能,来定义一个类型的别名,而且使用在模板的情况下,因此我们编译时要指定std=c++11

然后我们将list的别名Lst传入进入,就可以编译通过。

四、可变参数模板消除重复代码

  1. 泛型的打印函数
  2. 工厂函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值