I would like to do
我想做
template<typename... ArgTypes> void print(ArgTypes... Args)
{
print(Args)...;
}
And have it be equivalent to this quite bulky recursive chain:
它等价于这个庞大的递归链:
template<typename T, typename... ArgTypes> void print(const T& t, ArgTypes... Args)
{
print(t);
print(Args...);
}
followed by explicit single-parameter specializations for every type I'd like to print.
接下来是针对我想要打印的每种类型的显式单参数专门化。
The "problem" with the recursive implementation is that a lot of redundant code is generated, because each recursive step results in a new function of N-1
arguments, whereas the code I'd like to have would only generate code for a single N
-arg print
function, and have at most N
specialized print
functions.
递归实现的“问题”,生成大量的冗余代码,因为每个递归步骤的结果在一个新的N - 1的参数的函数,而我想要的代码只会生成代码为单个N-arg打印功能,和最多N专门打印功能。
3 个解决方案
#1
50
The typical approach here is to use a dumb list-initializer and do the expansion inside it:
这里的典型方法是使用哑列初始化器并在其中进行扩展:
{ print(Args)... }
Order of evaluation is guaranteed left-to-right in curly initialisers.
评估的顺序由左到右的卷曲初始化。
But print
returns void
so we need to work around that. Let's make it an int then.
但是打印返回无效,所以我们需要解决这个问题。我们把它变成一个整数。
{ (print(Args), 0)... }
This won't work as a statement directly, though. We need to give it a type.
不过,这并不能直接作为一种表述。我们需要给它一个类型。
using expand_type = int[];
expand_type{ (print(Args), 0)... };
This works as long as there is always one element in the Args
pack. Zero-sized arrays are not valid, but we can work around that by making it always have at least one element.
只要Args包中始终有一个元素,这就可以工作。零大小的数组是无效的,但是我们可以通过使它总是至少有一个元素来解决这个问题。
expand_type{ 0, (print(Args), 0)... };
We can make this pattern reusable with a macro.
我们可以使用宏使此模式可重用。
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));
However, making this reusable requires a bit more attention to some details. We don't want overloaded comma operators to be used here. Comma cannot be overloaded with one of the arguments void
, so let's take advantage of that.
但是,要使此可重用性更需要注意一些细节。我们不希望在这里使用重载的逗号运算符。逗号不能重载,因为其中一个参数无效,所以让我们利用它。
#define SO_EXPAND_SIDE_EFFECTS(PATTERN) \
::so::expand_type{ 0, ((PATTERN), void(), 0)... }
If you are
paranoid
afraid of the compiler allocating large arrays of zeros for naught, you can use some other type that can be list-initialised like that but stores nothing.
如果您非常担心编译器为零分配大量的0数组,那么您可以使用一些其他类型,这些类型可以像那样进行列表初始化,但不存储任何内容。
namespace so {
struct expand_type {
template <typename... T>
expand_type(T&&...) {}
};
}
#2
8
C++17 fold expression:
c++ 17褶皱表达式:
(f(args), ...);
Keep simple things simple ;-)
保持简单的简单;-)
If you call something that might return an object with overloaded comma operator use:
如果您调用某个可能返回具有重载逗号操作符的对象的东西,请使用:
((void)f(args), ...);
#3
7
You can use even more simple and readable approach
您可以使用更简单和可读的方法。
template<typename... ArgTypes> void print(ArgTypes... Args)
{
for (const auto& arg : {Args...})
{
print(arg);
}
}
I have played with both variants on compile explorer and both gcc and clang with O3 or O2 produce exactly the same code but my variant is obviously cleaner.
我在compile explorer上使用了这两个变体,gcc和clang与O3或O2产生的代码完全相同,但我的变体显然更干净。
#1
50
The typical approach here is to use a dumb list-initializer and do the expansion inside it:
这里的典型方法是使用哑列初始化器并在其中进行扩展:
{ print(Args)... }
Order of evaluation is guaranteed left-to-right in curly initialisers.
评估的顺序由左到右的卷曲初始化。
But print
returns void
so we need to work around that. Let's make it an int then.
但是打印返回无效,所以我们需要解决这个问题。我们把它变成一个整数。
{ (print(Args), 0)... }
This won't work as a statement directly, though. We need to give it a type.
不过,这并不能直接作为一种表述。我们需要给它一个类型。
using expand_type = int[];
expand_type{ (print(Args), 0)... };
This works as long as there is always one element in the Args
pack. Zero-sized arrays are not valid, but we can work around that by making it always have at least one element.
只要Args包中始终有一个元素,这就可以工作。零大小的数组是无效的,但是我们可以通过使它总是至少有一个元素来解决这个问题。
expand_type{ 0, (print(Args), 0)... };
We can make this pattern reusable with a macro.
我们可以使用宏使此模式可重用。
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));
However, making this reusable requires a bit more attention to some details. We don't want overloaded comma operators to be used here. Comma cannot be overloaded with one of the arguments void
, so let's take advantage of that.
但是,要使此可重用性更需要注意一些细节。我们不希望在这里使用重载的逗号运算符。逗号不能重载,因为其中一个参数无效,所以让我们利用它。
#define SO_EXPAND_SIDE_EFFECTS(PATTERN) \
::so::expand_type{ 0, ((PATTERN), void(), 0)... }
If you are
paranoid
afraid of the compiler allocating large arrays of zeros for naught, you can use some other type that can be list-initialised like that but stores nothing.
如果您非常担心编译器为零分配大量的0数组,那么您可以使用一些其他类型,这些类型可以像那样进行列表初始化,但不存储任何内容。
namespace so {
struct expand_type {
template <typename... T>
expand_type(T&&...) {}
};
}
#2
8
C++17 fold expression:
c++ 17褶皱表达式:
(f(args), ...);
Keep simple things simple ;-)
保持简单的简单;-)
If you call something that might return an object with overloaded comma operator use:
如果您调用某个可能返回具有重载逗号操作符的对象的东西,请使用:
((void)f(args), ...);
#3
7
You can use even more simple and readable approach
您可以使用更简单和可读的方法。
template<typename... ArgTypes> void print(ArgTypes... Args)
{
for (const auto& arg : {Args...})
{
print(arg);
}
}
I have played with both variants on compile explorer and both gcc and clang with O3 or O2 produce exactly the same code but my variant is obviously cleaner.
我在compile explorer上使用了这两个变体,gcc和clang与O3或O2产生的代码完全相同,但我的变体显然更干净。