C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++可变参数模板

C++11可变参数模板详解

作者:愚润泽

这篇文章给大家介绍C++11可变参数模板详解,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

一,什么是可变参数模板

什么是可变?
可变就是可以不同。可变参数,即:参数类型可变,参数个数可变。

二,基本语法

使用 typename...class... 声明模板参数包,...就代表是一个参数包:

template<typename T, typename... Args>
void myFunc(T first, Args... rest) { /*...*/ } // 使用模板的参数包

三,可变参数模板的使用

参数包作为整体

我们在使用时,可以往myFunc里传入不同个数和不同类型的参数。
编译器在编译的过程中,根据myFunc传入的不同参数,实例化出带有不同参数的myFunc函数
示例:

template<class ...Args>
void Myfunc(Args...args)
{
	cout << sizeof...(args) << endl;
}
int main()
{
	int x1 = 1;
	double x2 = 2.2;
	std::string s1 = "hello world";
	Myfunc(x1);  // 输出:1
	Myfunc(x1, x2);  // 输出:2
	Myfunc(x1, x2, s1);  // 输出:3
	return 0;
}

说明:

包展开

对于一个参数包,我们可以把他当做一个整体进行使用,如上面的sizeof...(args),我们就是直接把args当一个整体进行使用

如果我们想要拿到里面的每一个参数就需要用到包展开。直接将参数包依次展开依次作为实参给⼀个函数去处理。
【注意,参数包可不能args[i]这样下标访问使用。因为:参数包是在编译时就确定的一组参数。在编译时,编译器会根据具体的模板实例化来处理参数包,而不像数组那样在运行时存在于内存中】

错误示范1(不能args[i])

假设你想打印出函数参数包里面的所有参数。

template<class ...Args>
void Print(Args...args)
{
	cout << args... << endl;
}

你这样肯定不行,会报错:“args”: 未声明的标识符。因为参数列表里面args在编译时早就被具体实例化成了有具体类型的函数了,如Print(1.1, 2)就被实例化成:Print(double x1, int x2);

普通一次展开

在使用递归展开的时候,我们先来看一个简单的展开:

template<class T1, class T2, class...Args>
void ShowList(T1 x1, T2 x2, Args...args)
{
	cout << x1 << endl;
	cout << x2 << endl;
	cout << sizeof...(args) << endl;
}
int main()
{
	ShowList(1, 2.2, 3.3, "hello world");
	return 0;
}

输出结果:

在这里,我们传入一组参数,其中12.2分别给了x1x2,后面的3.3"hello world"被打包给了args

递归展开

写法三大要点:

示例:
Print 打印出函数参数包里面的所有参数

// 递归终止函数:当参数列表为空时匹配这个函数
void ShowList()
{
	cout << endl;
}
// 递归包展开函数
template<class T, class...Args>
void ShowList(T x, Args...args)
{
	cout << x << " "; // 打印被展开的单个参数
	ShowList(args...);
}
template<class...Args>
void Print(Args... args)
{
	ShowList(args...);
}
int main()
{
	Print(1, string("xxxxx"), 2.2);
	return 0;
}

运行结果:

1 xxxxx 2.2

具体编译时实例化理解图:

当然也可以编写有参的结束函数:

//递归终止函数
template<class T>
void ShowList(T x)
{
	cout << x << endl;
}

ShowList参数个数为1的时候编译器会找最匹配的,也就是调到这个ShowList(T x),然后执行完以后,ShowList(T x)里面没有再调用ShowList于是完成终止

错误示范2(if运行时判断)

那在递归函数里面,利用sizeof...(args) == 0来终止可以吗?
答案是不可以!!!

template<class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " "; //打印传入的若干参数中的第一个参数
	if (sizeof...(args) == 0)
	{
		return;
	}
	ShowList(args...);    //将剩下参数继续向下传
}
int main()
{
	ShowList(1, 2.2, 3.3, "hello world");
	return 0;
}

报错:“ShowList”: 未找到匹配的重载函数

为什么 if (sizeof...(args) == 0) 无法终止递归(以下内容由AI生成):

1. sizeof...(args) 是编译期常量,但 if 是运行时判断

2. 编译器必须实例化 ShowList(args...)
即使 if 条件在运行时是 false,编译器仍然要 确保 ShowList(args...) 的调用是合法的,因为:

3. 当 args... 为空时,ShowList() 无匹配版本

解决方法:

方法1 : 像之前介绍的一样,额外写第一个终止函数。

if constexpr 编译时判断解决问题

方法 2:用 if constexpr(C++17 编译时判断)【已验证有效】

template<class T, class... Args>
void ShowList(T value, Args... args) {
    cout << value << " ";
    if constexpr (sizeof...(args) > 0) {  // 编译时判断
        ShowList(args...);               // 仅在 args 非空时实例化
    }
    // 否则自动终止
}

非递归展开

示例:

template <class T>
const T& GetArg(const T& x) {
    cout << x << " ";  // 打印参数
    return x;          // 返回原参数(保证类型不变)
}
template <class ...Args>
void Arguments(Args... args) {}  // 空函数,仅用于接收展开后的参数包
template <class ...Args>
void Print(Args... args) {
    Arguments(GetArg(args)...);  // 关键:展开参数包并调用 GetArg 处理每个参数
}
int main()
{
    Print(1, 2.2, 3.3, "hello world");
    return 0;
}

到此这篇关于C++11可变参数模板的文章就介绍到这了,更多相关C++11可变参数模板内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文