如何在所有可变参数模板参数上调用函数?
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 <typename... T> expand_type(T&&...)
? (或者由于编译器优化,是否存在一些潜在的危险副作用?)
函数调用不保证评估顺序。
-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 产生完全相同的代码,但我的变体显然更干净。
【讨论】:
不管传入的是什么,这都不复制吗?当print
ing 一个巨大的物体时,这不是很致命吗?
Als,你的链接指向一个不相关的 sn-p。
虽然这确实看起来很漂亮...很漂亮,但您可能应该编写类似 [...]using value_type = std::common_type_t<Args...>; for (auto const &arg : static_cast<value_type>(Args)...)
[...] 的内容,以便允许它与异构参数包一起使用。另外@rubenvb 我相信您的担忧是关于 std::initializer_list 被复制初始化?
那当然是std::common_type_t<ArgTypes...>
。
如果没有参数,这行得通吗?以上是关于如何在所有可变参数模板参数上调用函数?的主要内容,如果未能解决你的问题,请参考以下文章