【问题标题】: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 函数。

【问题讨论】:

    标签: c++ c++11 variadic-templates


    【解决方案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;
    • 如果没有参数,这行得通吗?
    猜你喜欢
    • 2021-10-01
    • 2021-04-10
    • 2018-10-25
    • 1970-01-01
    • 2017-10-12
    • 2011-08-26
    • 2016-12-01
    • 2018-03-20
    相关资源
    最近更新 更多