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

rub*_*nvb 43 c++ variadic-templates c++11

我想要做

template<typename... ArgTypes> void print(ArgTypes... Args)
{
   print(Args)...;
}
Run Code Online (Sandbox Code Playgroud)

并且它相当于这个相当庞大的递归链:

template<typename T, typename... ArgTypes> void print(const T& t, ArgTypes... Args)
{
  print(t);
  print(Args...);
}
Run Code Online (Sandbox Code Playgroud)

然后是我想要打印的每种类型的显式单参数专精.

递归实现的"问题"是生成了大量冗余代码,因为每个递归步骤都会产生一个新的N-1参数函数,而我想要的代码只会为单个N-arg print函数生成代码,并且具有最多的N专业print功能.

R. *_*des 69

C++ 17倍表达式

(f(args), ...);
Run Code Online (Sandbox Code Playgroud)

如果你调用的东西可能会返回一个带有重载的逗号运算符的对象:

((void)f(args), ...);
Run Code Online (Sandbox Code Playgroud)

Pre-C++ 17解决方案

这里的典型方法是使用哑列表初始化器并在其中进行扩展:

{ print(Args)... }
Run Code Online (Sandbox Code Playgroud)

在卷曲初始化程序中,评估顺序从左到右保证.

但是print回报void所以我们需要解决这个问题.让我们把它变成一个int.

{ (print(Args), 0)... }
Run Code Online (Sandbox Code Playgroud)

但是,这不会直接作为声明.我们需要给它一个类型.

using expand_type = int[];
expand_type{ (print(Args), 0)... };
Run Code Online (Sandbox Code Playgroud)

只要Args包中总有一个元素,这就可以工作.零大小的数组无效,但我们可以通过使它始终至少有一个元素来解决这个问题.

expand_type{ 0, (print(Args), 0)... };
Run Code Online (Sandbox Code Playgroud)

我们可以使用宏来重复使用此模式.

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));
Run Code Online (Sandbox Code Playgroud)

但是,使这种可重用性需要更多关注一些细节.我们不希望在这里使用重载的逗号运算符.逗号不能用其中一个参数重载void,所以让我们利用它.

#define SO_EXPAND_SIDE_EFFECTS(PATTERN) \
        ::so::expand_type{ 0, ((PATTERN), void(), 0)... }
Run Code Online (Sandbox Code Playgroud)

如果你是偏执狂害怕编译器分配大量零的零,你可以使用其他类型的列表初始化,但不存储任何东西.

namespace so {
    struct expand_type {
        template <typename... T>
        expand_type(T&&...) {}
    };
}
Run Code Online (Sandbox Code Playgroud)

  • 有趣的是,更高级的C++将不可避免地导致更高级的黑客攻击,以一种简洁明了的方式解决不可能实现的问题.谢谢你写这个! (9认同)
  • 函数调用不保证评估顺序.`{}`-initialization呢. (6认同)
  • 绝对精彩的答案:) (3认同)

Ben*_*uch 15

C++ 17倍表达式:

(f(args), ...);
Run Code Online (Sandbox Code Playgroud)

保持简单的事情简单;-)

如果你调用的东西可能会返回一个带有重载的逗号运算符的对象:

((void)f(args), ...);
Run Code Online (Sandbox Code Playgroud)

  • 我已经在接受的答案中添加了C ++ 17解决方案,谢谢您的建议;-) (3认同)
  • @Silicomancer 它是一个 C++17 折叠表达式,因此它是折叠表达式语法所必需的。 (2认同)

Ant*_*nko 7

您可以使用更简单易读的方法

template<typename... ArgTypes> void print(ArgTypes... Args)
{
   for (const auto& arg : {Args...})
   {
      print(arg);
   }
}
Run Code Online (Sandbox Code Playgroud)

我在编译浏览器中使用了两种变体,并且使用O3或O2的gcc和clang产生完全相同的代码,但我的变体显然更清晰.

  • 虽然这确实看起来很漂亮......很漂亮,你应该写一些像[...]`使用value_type = std :: common_type_t <Args ...>; for(auto const&arg:{static_cast <value_type>(Args)...})`[...]以便允许它使用异构参数包.此外@rubenvb我相信你的担忧有关的std :: initializer_list <>被拷贝初始化? (2认同)