【问题标题】:Multiply variable number of arguments乘以可变数量的参数
【发布时间】:2016-11-27 18:59:00
【问题描述】:

假设我有不同数量的参数,我想将它们相乘。我想到的第一种方式是递归算法:

template<typename Head>
u64 Multiply(Head head) const
{
    return head;
}

template<typename Head, typename... Tail>
u64 Multiply(Head head, Tail... tail) const
{
    return head * Multiply(tail...);
}

但后来我看到了这个技巧:

// u32 and u64 are 32 and 64 bit unsigned integers.
template<typename... T>
u64 Multiply(T... args)
{
    u64 res = 1;
    for (const u32& arg: {args...})
        res *= arg;
    return res;
}

第二个对我来说似乎更好。更容易阅读。但是,这对性能有何影响?有什么被复制的吗? {args...} 首先是做什么的?有没有更好的方法?

我可以使用 C++14。

编辑要清楚:这是关于运行时乘法,而不是编译时。

更清楚一点:我不想计算整数(尽管这是我当前的应用程序),但我发现的算法专门用于整数。

更多:参数属于同一类型。没有这种限制的算法会非常有趣,但可能会针对不同的问题。

【问题讨论】:

  • 我认为,当 mixed 整数类型的值传递给函数时,第二种方法可能不起作用,例如:Multiply(1, 1u, 1ul, 1ull);
  • 第二个函数假定T是整数类型,第一个函数不是这样
  • @Snps 为什么#1 应该在编译时?它是运行时,就像#2
  • 或者等待C++17和return (args * ... * 1);
  • @Rakete1111:因为它是毫无意义的椭圆,没有任何好处,让我停下来思考的时间比我想的要长:-S

标签: c++ templates c++14


【解决方案1】:

这里有多个问题:

  1. 对性能有什么影响?不知道。你需要测量。但是,根据参数的类型,我可以想象编译器会完全优化任何一种方式:它确实知道参数的数量和类型。
  2. 什么是{ args... }?好吧,它为常见的参数类型创建了一个std::initializer_list&lt;T&gt;(假设有一个)。不过,您可能希望将值与 std::common_type_t&lt;T...&gt; 一起使用,而不是固定类型。
  3. 有更好的方法吗?虽然我可以想象编译器实际上在这种扩展方面做得相当好,但有几种方法。立即想到的替代方案是 return (args * ... * 1);,然而,它需要 C++17。如果至少有一个参数,则可以省略 * 1:如果可变参数为空列表,则可以避免编译时错误。

【讨论】:

  • 参数包为空时没有编译错误,请看open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4295.html。它也默认为 1,所以* 1 是不必要的
  • @Rakete1111 this 陈述了一些完全相反的情况......虽然不确定哪个是正确的
  • @W.F.也许该提案在被接受之前被委员会修改了,并且没有发布更新的论文来反映这种变化?
  • @Rakete1111:我知道他们搞砸了默认值,以防有一个空的参数包。不过,我并没有太注意讨论。例如,this paper.
  • 如果我理解正确,使用此方法创建初始化列表会复制参数吗?除了编译器优化。
【解决方案2】:

代码

template<typename... T>
u64 Multiply(T... args)
{
    u64 res = 1;
    for (const u32& size : {args...})
        res *= size;
    return res;
}

对我来说有点神秘 :-) 为什么我们有 T 类型的模板参数并且在我们使用固定大小值的方法内部?而变量名size 看起来非常晦涩,因为这个变量与任何类型的大小都无关。如果您将浮点数据提供给模板,那么在内部使用整数类型也不是一个有效的假设。

好的,但要回答你的问题:

第一个可以与您放入模板函数的所有类型一起使用。第二个使用固定(无符号整数)类型,如果我看到模板本身的声明,这不是我所期望的。

正如我现在所了解的那样,这两个版本都可以制作为 constexpr :-) 并且在编译时间计算方面工作得很好。

从您的评论中回答问题:

{args...}

扩展到:

{ 1,2,3,4}

这只是一个“数组”(std::std::initializer_list),只有在所有元素都具有相同类型时才有效。

所以有

for (const u32& size : {args...})

简单地遍历数组。

【讨论】:

  • "您的递归示例将在编译时完成这项工作!"不,它不会在编译时完成这项工作,如果编译器很聪明,它可能会被优化。
  • 你实际上可以让第二个在编译时运行,如果你让它constexpr :)
  • @Rakete1111:是的,放宽了 constexpr 规则...我会更改我的帖子 :-) 感谢您的建议!
  • 感谢您的解释,但这并不完全是我想要的。我知道这两个函数的用途,但我不明白第二个函数是如何执行的以及 {args...} 是如何工作的。关于大小变量名称,这是我用它做的一些测试的剩余部分:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-04
  • 2021-12-26
  • 1970-01-01
  • 2017-04-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多