我想做
template<typename... ArgTypes> void print(ArgTypes... Args)
{
print(Args)...;
}
并且它相当于这个相当庞大的递归链:
template<typename T, typename... ArgTypes> void print(const T& t, ArgTypes... Args)
{
print(t);
print(Args...);
}
后面是每种类型的明确的单参数专业化,我想要打印。
&#34;问题&#34;使用递归实现是生成了大量冗余代码,因为每个递归步骤都会产生N-1
个参数的新函数,而我想要的代码只会为单个{生成代码{1}} - arg N
函数,最多只有print
个N
个函数。
答案 0 :(得分:60)
C ++ 17倍表达
(f(args), ...);
如果你调用的东西可能会使用重载的逗号运算符返回一个对象:
((void)f(args), ...);
Pre-C ++ 17解决方案
这里的典型方法是使用哑列表初始化程序并在其中进行扩展:
{ print(Args)... }
评估顺序在卷曲初始化程序中从左到右保证。
但print
会返回void
,所以我们需要解决这个问题。让我们把它变成一个int。
{ (print(Args), 0)... }
但这不会直接作为声明。我们需要给它一个类型。
using expand_type = int[];
expand_type{ (print(Args), 0)... };
只要Args
包中总有一个元素,这就有效。零大小的数组无效,但我们可以通过使它始终至少有一个元素来解决这个问题。
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)... }
如果你是偏执狂害怕编译器分配大量零的零,你可以使用其他类型的列表初始化,但不存储任何内容。
namespace so {
struct expand_type {
template <typename... T>
expand_type(T&&...) {}
};
}
答案 1 :(得分:14)
C ++ 17倍表达式:
(f(args), ...);
简单易懂; - )
如果你调用的东西可能会使用重载的逗号运算符返回一个对象:
((void)f(args), ...);
答案 2 :(得分:7)
您可以使用更简单易读的方法
template<typename... ArgTypes> void print(ArgTypes... Args)
{
for (const auto& arg : {Args...})
{
print(arg);
}
}
我在compile explorer上使用了两种变体,而使用O3或O2的gcc和clang都生成完全相同的代码,但我的变体显然更清晰。