之前之前出过这方面的不少,我决定全部联系在一起,详细的说一下,可变参数和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可放心食用此文章