【C++】可变参列表展开
1.
sizeof... 操作符获取参数包args中参数个数
sizeof...(args);
2. 参数包定义
typename...:用于声明一个参数包,表示一组类型。它通常出现在模板参数列表中。typename... Args:声明了一个参数包,表示一组类型Args... args:声明了一个参数包,表示一组值
template<typename... Args>
void function(Args... args);
3. 参数包展开
-
参数包必须在编译期展开。运行时不可
-
参数包展开是 C++11 引入的可变参数模板(Variadic Templates)的一个重要特性。它允许你对参数包中的每个参数依次执行某个操作。展开操作符 ... 用于指示编译器将参数包中的每个参数依次替换到表达式中,生成多个独立的表达式。
-
总的来说:C++17以下使用递归或初始化列表法。C++17及以上使用折叠表达式或逗号表达式法。
以表达式 (std::cout << args << " ", 0)... 为例:
假设 args 是 {1, 2.5, "Hello", 'c', true},那么展开后会变成:
(std::cout << 1 << " ", 0),
(std::cout << 2.5 << " ", 0),
(std::cout << "Hello" << " ", 0),
(std::cout << 'c' << " ", 0),
(std::cout << true << " ", 0);
- 在C++17之前,处理参数包通常需要递归模板函数。在现代 C++ 中,尽量避免使用递归展开,因为递归展开可能导致编译时间增加和调试信息复杂。优先使用折叠表达式或逗号展开,这些方法更高效且易于维护。
3.1 递归模板展开
- 低于C++17时写法
- 递归模板展开是通过递归调用来处理参数包中的每个元素。
- 必须定义一个递归终止条件(如无参数的 print() 函数,或只有1个参数的print()),否则会导致无限递归。
// 当参数包为空时 void print() { std::cout << std::endl; } /* //参数包只剩一个参数时,也可 template<typename First> void print(First first) { std::cout << first <<std::endl; } */ template<typename First, typename... Rest> void print(First first, Rest... rest) { std::cout << first << " "; print(rest...); // 递归调用,处理剩余的参数 }
3.2 折叠表达式展开
-
C++17起的折叠表达式。若能使用C++17及以上使用该法。
-
折叠表达式可以简洁地展开参数包,并对每个参数执行相同的操作。
-
折叠表达式的展开规则:
左折叠 (args op ...) 的展开形式是:((a1opa2)opa3)op...opan
右折叠 (... op args) 的展开形式是:a1op(a2op(a3op...opan)) -
折叠表达式不仅支持算术运算符,还支持逻辑运算符、逗号运算符等。
//算术运算符
#include <iostream>
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 左折叠
}
int main() {
std::cout << sum(1, 2, 3, 4) << std::endl; // 输出:10
return 0;
}
//逻辑运算符
template<typename... Args>
bool all(Args... args) {
return (... && args); // 右折叠,所有参数都为 true
}
template<typename... Args>
bool any(Args... args) {
return (... || args); // 右折叠,任意参数为 true
}
3.3 逗号表达式展开
将折叠展开作用于逗号操作符,即每个参数arg都会带入逗号表达式中。
逗号操作符(,)的折叠表达式,无论是左折叠还是右折叠,最终的展开效果是相同的,因为逗号操作符的特性使得每个表达式都会被依次执行,且只返回最后一个表达式的值。
每个arg都执行一次操作符,这里的操作符是逗号操作符。
template<typename... Args>
void print(Args... args) {
(...,(std::cout << args << " ")); //右折叠
((std::cout << args << " "), ...); // 左折叠
}
3.4 初始化列表展开
- 通过初始化列表和逗号表达式可以展开参数包,是在 C++11 没有折叠表达式 时,用 std::initializer_list 展开参数包的“黑魔法”写法
/*
① 在 C++ 中,逗号表达式 (a, b) 的结果是 b 的值,但会先执行 a。因此,(std::cout << args << " ", 0) 的结果是 0,但会先执行 std::cout << args << " "等语句。
② std::initializer_list<int>{(std::cout << args << " ", 0)...} 是一个初始化列表,它通过展开参数包 args... 来构造一个 std::initializer_list<int> 对象。每个参数 args 通过逗号表达式 (std::cout << args << " ", 0) 被处理,最终生成一个整数 0,并将其放入初始化列表中。所以初始化列表本身存储的值是展开次数个0。
③ void() 的作用是确保最外层逗号表达式表达式的结果是 void 类型。如果不加 void(),初始化列表的构造表达式会返回一个 std::initializer_list<int> 对象,这可能会导致编译器对返回值进行不必要的处理,甚至可能引发警告或错误。
*/
template <typename... Args>
void print(Args... args)
{
//这种逗号表达式后直接跟...的写法
(std::initializer_list<int>{(
std::cout << args << " ",
std::cout.flush(),
0)...}, void());
}
/*
//也可,这是折叠表达式写法。但是没有必要,若C++17及以上,就不需要用初始化列表
(std::initializer_list<int>{((
std::cout << args << " ",
std::cout.flush(),
0),...)}, void());
*/
int main() {
print(1, 2.5, "Hello", 'c', true);
return 0;
}

浙公网安备 33010602011771号