如何在所有可变模板args上调用函数?

时间:2023-01-24 11:28:41

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产生的代码完全相同,但我的变体显然更干净。