C++可变参数与折叠表达式详解

之前之前出过这方面的不少,我决定全部联系在一起,详细的说一下,可变参数和C++17的折叠表达式的实现和众多用途。包括但不限于变长模板类,变参模板函数,变参变量模板。

我们不再讲述老式的解包方式,全部使用折叠表达式解包。

可变参数是什么?无非是一个能接收理论上无数参数并且处理的一种方式,几乎所有语言都提供这种方式,不管是C,C++,Python。我都使用过。Python的没什么好说的,也不是重点。

得益与模板的强大,C++内不但能处理任意数量参数,同时能处理任意类型。

先从最基础的函数开始,代码如下

#include<iostream>
template<typename... Args>
auto left_sub(Args&&... args) {
    return (... - args);//左折叠
}

template<typename... Args>
auto right_sub(Args&&... args) {
    return (args - ...);//右折叠
}

template<typename...Args>
auto print(Args&&...args) {
    (std::cout << ... << args) << std::endl;//打印输出的折叠表达式得使用小括号
}
int main() {
    auto a = left_sub(2, 3, 4, 5);  //-10
    auto b = right_sub(2, 3, 4, 5); //-2
    std::cout << a << " " << b << std::endl;

    print(1, 2, 3, 4, 5, 6, "哈哈哈");
}

折叠表达式可以处理所有运算符,我们这里只是用-举例子。

很多看似高级的操作实际上底层原理十分简单

#ifdef INSIGHTS_USE_TEMPLATE
template<>
int left_sub<int, int, int, int>(int && __args0, int && __args1, int && __args2, int && __args3)
{
  return ((__args0 - __args1) - __args2) - __args3;
}
#endif

这是左折叠第一个函数在编译期替换的代码,没什么好说的,这就是左折叠接收参数并且处理运算的方式,从左到右的计算。

#ifdef INSIGHTS_USE_TEMPLATE
template<>
int right_sub<int, int, int, int>(int && __args0, int && __args1, int && __args2, int && __args3)
{
  return __args0 - (__args1 - (__args2 - __args3));
}
#endif

这是右折叠在编译期展开的代码,其实就是从右边开始计算,2-(3-(4-5)),也就是-2。

下一个函数,print,展开代码如下

#ifdef INSIGHTS_USE_TEMPLATE
template<>
void print<int, int, int, int, int, int, char const (&)[10]>(int && __args0, int && __args1, int && __args2, int && __args3, int && __args4, int && __args5, char const (&args)[10])
{
  std::operator<<(std::cout.operator<<(__args0).operator<<(__args1).operator<<(__args2).operator<<(__args3).operator<<(__args4).operator<<(__args5), args).operator<<(std::endl);
}
#endif

 展开有一些长,不过其实就是按照顺序打印,仅此而已,我们使用括号把折叠表达式括起来是为了编译期展开的时候不出现错误,仅此而已。

那我们讲讲变长模板类和变参变量模板

#include<iostream>
//变长模板类折叠表达式解包
template<auto... args>
struct Mul
{
	constexpr static auto value = (... * args);
};

//变参变量模板,折叠表达式解包
template<auto... args>
constexpr auto Mul_ = (...*args);

int main() {
	std::cout << Mul<5, 6, 7, 8, 10.0>::value << std::endl;

	std::cout << Mul_<5, 6, 7, 8, 10.0> << std::endl;

	system("pause");
}

如果不知道什么是变量模板请先阅读http://t.csdn.cn/gsJ6b

如果看懂了之前写的,相信这些代码简直不要太简单,那我们直接放编译替换出来的代码

#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct Mul<5, 6, 7, 8, 10>
{
  inline static constexpr const int value = (((5 * 6) * 7) * 8) * 10;
};
#endif


template<>
constexpr const int Mul_<5, 6, 7, 8, 10> = (((5 * 6) * 7) * 8) * 10;

友情提示,因为我的工具只支持C++17所以把10.0换成了10,C++17的模板参数只支持整数
相信第一个函数肯定是都看的懂,那么变量模板这个呢?没啥区别,学过模板特例化和对模板推导有些了解的都能明白它在编译期进行的实例化替换。

最后强调几点,模板参数的args才是参数包,可以使用sizeof...来计算参数个数,举个例子

void func() {}
template<class T, class ...argv>
void func(T num, argv...args)
{
	//cout << "num=" << sizeof...(args) << endl;
	cout << num << endl;
	func(args...);
}

这里用的老式的递归解包,设置出口。

那我们提出一个问题,如何让我们之前写的printf打印的代码之间有间隔呢?加小括号,如下代码

#include<iostream>
template<typename...Args>
void print(Args&&...args) {
    ((std::cout << args << ' '), ...);
}
int main() {
    print(1, 2, 3, 4, 5, 6, "离谱");
}

下面是编译期展开的代码

#ifdef INSIGHTS_USE_TEMPLATE
template<>
void print<int, int, int, int, int, int, char const (&)[7]>(int && __args0, int && __args1, int && __args2, int && __args3, int && __args4, int && __args5, char const (&args)[7])
{
(std::operator<<(std::cout.operator<<(__args0), ' ')) , 

((std::operator<<(std::cout.operator<<(__args1), ' ')) , 

((std::operator<<(std::cout.operator<<(__args2), ' ')) , 


((std::operator<<(std::cout.operator<<(__args3), ' ')) , 

((std::operator<<(std::cout.operator<<(__args4), ' ')) , 

((std::operator<<(std::cout.operator<<(__args5), ' ')) , 

(std::operator<<(std::operator<<(std::cout, args), ' ')))))));

}
#endif

有些长,但是其实原理很简单,看懂了前面的自然也能看懂这些,那么大家估计对...这个东西有一些疑惑了,这玩意好像编译展开的时候从来没有用到过,我们写这个干嘛的?

我个人的理解是,就是告诉编译器这是一个可变参,仅此而已。

如有错误还请指正,其实还是需要一点经验来看这些模板的,编译器最少支持C++17可放心食用此文章

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C++ 中,可变参数和非可变参数的概念也存在。 非可变参数就是指函数在定义时,参数的数量和类型已经确定,调用时需要传入和定义时一样数量和类型的参数。例如: ```cpp int add(int x, int y) { return x + y; } int result = add(2, 3); std::cout << result << std::endl; // 输出 5 ``` 可变参数C++ 中通常使用可变参数模板实现,即模板参数数量不确定,参数类型也不确定,需要在函数调用时确定具体类型和数量。C++11 引入了 `std::initializer_list`,可以方便地实现可变参数模板。 例如,下面是一个求和函数的可变参数实现: ```cpp #include <iostream> #include <initializer_list> template <typename T> T sum(std::initializer_list<T> args) { T result = 0; for (auto i : args) { result += i; } return result; } int main() { auto result1 = sum({2, 3}); auto result2 = sum({2, 3, 4, 5}); std::cout << result1 << std::endl; // 输出 5 std::cout << result2 << std::endl; // 输出 14 return 0; } ``` 需要注意的是,可变参数模板必须放在函数参数列表的最后,否则编译器无法正确推断类型。同时,可变参数模板也可以和非可变参数一起使用。例如: ```cpp template <typename T> T add(T x, T y) { return x + y; } template <typename T, typename... Args> T sum(T first, Args... args) { return add(first, sum(args...)); } int main() { auto result1 = sum(2, 3); auto result2 = sum(2, 3, 4, 5); std::cout << result1 << std::endl; // 输出 5 std::cout << result2 << std::endl; // 输出 14 return 0; } ``` 在这个例子中,`sum` 函数的第一个参数是非可变参数,后面的参数可变参数。函数的实现中,可变参数使用了递归调用,将第一个参数和后面的参数分别相加,最终得到总和。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值