C++ 如何在所有可变模板参数上调用函数?
我想做什么C++ 如何在所有可变模板参数上调用函数?,c++,c++11,variadic-templates,C++,C++11,Variadic Templates,我想做什么 template<typename... ArgTypes> void print(ArgTypes... Args) { print(Args)...; } 模板无效打印(ArgTypes…Args) { 打印(Args)。。。; } 并将其等效于这个相当庞大的递归链: template<typename T, typename... ArgTypes> void print(const T& t, ArgTypes... Args) {
template<typename... ArgTypes> void print(ArgTypes... Args)
{
print(Args)...;
}
模板无效打印(ArgTypes…Args)
{
打印(Args)。。。;
}
并将其等效于这个相当庞大的递归链:
template<typename T, typename... ArgTypes> void print(const T& t, ArgTypes... Args)
{
print(t);
print(Args...);
}
模板无效打印(常量T&T、ArgTypes…Args)
{
打印(t);
打印(Args…);
}
然后是我想要打印的每种类型的显式单参数专门化
递归实现的“问题”是生成了大量冗余代码,因为每个递归步骤都会产生一个包含
N-1
参数的新函数,而我想要的代码只会为单个N
-argprint
函数生成代码,最多有N
专门的打印
功能。C++17倍表达式
(f(args), ...);
如果调用可能返回带有重载逗号运算符的对象,请使用:
((void)f(args), ...);
((void)f(args), ...);
C++17之前的解决方案
(f(args), ...);
这里的典型方法是使用哑列表初始值设定项并在其中进行扩展:
{ print(Args)... }
在卷曲初始化器中,求值顺序保证从左到右
但是print
返回void
,所以我们需要解决这个问题。让我们把它设为int
{ (print(Args), 0)... }
不过,这不能直接作为一个声明来使用。我们需要给它一个类型
using expand_type = int[];
expand_type{ (print(Args), 0)... };
只要Args
pack中始终有一个元素,这种方法就可以工作。零大小的数组无效,但我们可以通过使它始终至少有一个元素来解决这一问题
expand_type{ 0, (print(Args), 0)... };
我们可以通过宏使此模式可重用
namespace so {
using expand_type = int[];
}
#define SO_EXPAND_SIDE_EFFECTS(PATTERN) ::so::expand_type{ 0, ((PATTERN), 0)... }
// usage
SO_EXPAND_SIDE_EFFECTS(print(Args));
然而,要使其可重用,需要更多地关注一些细节。我们不希望在这里使用重载逗号运算符。逗号不能用一个参数void
重载,所以让我们利用它
#define SO_EXPAND_SIDE_EFFECTS(PATTERN) \
::so::expand_type{ 0, ((PATTERN), void(), 0)... }
如果您担心编译器会将大量的零数组分配给NOUT,那么您可以使用其他类型,可以像那样进行列表初始化,但不存储任何内容
namespace so {
struct expand_type {
template <typename... T>
expand_type(T&&...) {}
};
}
so{
结构扩展类型{
模板
展开_类型(T&&…{}
};
}
您可以使用更简单易读的方法
template<typename... ArgTypes> void print(ArgTypes... Args)
{
for (const auto& arg : {Args...})
{
print(arg);
}
}
模板无效打印(ArgTypes…Args)
{
for(常量自动和参数:{Args…})
{
打印(arg);
}
}
我在上玩过这两个变体,gcc和带有O3或O2的clang产生的代码完全相同,但我的变体显然更干净。C++17倍表达式:
(f(args), ...);
让简单的事情变得简单;-)
如果调用可能返回带有重载逗号运算符的对象,请使用:
((void)f(args), ...);
((void)f(args), ...);
“但不存储任何内容”--编译器可能会为此分配相同数量的空间,因为参数也需要空间(对于调用堆栈信息等,可能需要更多空间)。如果你推测优化后,我认为数组存储可能会被优化。有趣的是,一个更先进的C++将不可避免地导致更先进的黑客以一种清晰而简洁的方式来处理不可能的事情。谢谢你写这篇文章!绝对精彩的回答:)也可以只调用一个空函数
名称空间{template expand_type(T&&…{}}
?(或者由于编译器优化,是否存在一些潜在的危险副作用?)函数调用不能保证计算顺序<代码>{}-初始化可以。不管传入了什么,它都不会复制吗?当打印一个巨大的对象时,这难道不是非常致命吗?Als,你的链接指向一个不相关的片段。虽然这看起来确实很漂亮。。。很好,您可能应该使用value\u type=std::common\u type\t编写类似[…]的代码;for(auto const&arg:{static_cast(Args)…})
[…],以便允许它使用异构参数包。另外@rubenvb我相信您关心的是std::initializer\u列表被复制初始化?这当然是std::common\u type\t
。这是公认答案的一部分吗?真的是C++17的最佳答案我已经在公认的答案中添加了C++17解决方案,谢谢你的建议;-)为什么逗号折叠需要外括号?@Silicomancer是一个C++17折叠表达式,因此折叠表达式语法需要它。