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

Posted

技术标签:

【中文标题】如何在所有可变参数模板参数上调用函数?【英文标题】:How to call a function on all variadic template args? 【发布时间】:2013-06-24 18:08:52 【问题描述】:

我想做

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...);

然后是我想打印的每种类型的显式单参数特化。

递归实现的“问题”是生成了很多冗余代码,因为每个递归步骤都会产生一个新函数 N-1 参数,而我想要的代码只会生成代码单个N-arg print 函数,最多有N 专门的print 函数。

【问题讨论】:

【参考方案1】:

C++17 折叠表达式

(f(args), ...);

如果您调用可能返回带有重载逗号运算符的对象的内容:

((void)f(args), ...);

C++17 之前的解决方案

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

 print(Args)... 

在 curly 初始化程序中,评估顺序保证从左到右。

但是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&&...) 
    ;

【讨论】:

“但什么都不存储”——编译器可能会为此分配相同数量的空间,因为参数也需要空间(可能更多,用于调用堆栈信息等)。如果你推测它会被优化出来,我认为阵列存储也很可能会被优化出来。 有趣的是,更高级的 C++ 将不可避免地导致更高级的 hack 以清晰简洁的方式解决不可能的事情。谢谢你写这篇文章! 绝对精彩的答案:) 是否也可以只调用一个空函数namespace so template &lt;typename... T&gt; expand_type(T&amp;&amp;...) ? (或者由于编译器优化,是否存在一些潜在的危险副作用?) 函数调用不保证评估顺序。 -initialization 可以。【参考方案2】:

C++17折表达式:

(f(args), ...);

让简单的事情简单;-)

如果您调用的东西可能会返回带有重载逗号运算符的对象,请使用:

((void)f(args), ...);

【讨论】:

这可以作为已接受答案的一部分吗?真的是 C++17 的最佳答案 我已将 C++17 解决方案添加到已接受的答案中,感谢您的建议 ;-) 为什么逗号折叠需要外括号? @Silicomancer 它是一个 C++17 折叠表达式,因此它需要折叠表达式语法。【参考方案3】:

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

template<typename... ArgTypes> void print(ArgTypes... Args)

   for (const auto& arg : Args...)
   
      print(arg);
   

我在compile explorer 上使用过这两种变体,gcc 和 clang 与 O3 或 O2 产生完全相同的代码,但我的变体显然更干净。

【讨论】:

不管传入的是什么,这都不复制吗?当printing 一个巨大的物体时,这不是很致命吗? Als,你的链接指向一个不相关的 sn-p。 虽然这确实看起来很漂亮...很漂亮,但您可能应该编写类似 [...]using value_type = std::common_type_t&lt;Args...&gt;; for (auto const &amp;arg : static_cast&lt;value_type&gt;(Args)...)[...] 的内容,以便允许它与异构参数包一起使用。另外@rubenvb 我相信您的担忧是关于 std::initializer_list 被复制初始化? 那当然是std::common_type_t&lt;ArgTypes...&gt; 如果没有参数,这行得通吗?

以上是关于如何在所有可变参数模板参数上调用函数?的主要内容,如果未能解决你的问题,请参考以下文章

C/C++中的可变参数和可变参数模板

如何使用可变参数模板参数保存可变数量的参数?

具有模板函数名称的可变参数模板

如何将元组扩展为可变参数模板函数的参数?

如何调用所有可变继承类的函数?

为每个可变参数模板参数和一个数组调用一个函数