【问题标题】:Calculate the average of several values using a variadic-template function使用可变参数模板函数计算多个值的平均值
【发布时间】:2014-07-29 17:06:29
【问题描述】:

我正在尝试编写一个函数来确定任意数量的参数的平均值,所有这些参数都具有相同的类型。出于学习目的,我尝试使用可变参数模板函数来做到这一点。

这是我目前所拥有的:

template<typename T, class ... Args>
T Mean(Args ... args)
{
    int numArgs = sizeof...(args);
    if (numArgs == 0)
        return T();           // If there are no arguments, just return the default value of that type

    T total;
    for (auto value : args...)
    {
        total += value;
    }

    return total / numArgs;   // Simple arithmetic average (sum divided by total)
}

当我尝试编译它(使用 MS Visual Studio 2013)时,我收到以下编译错误:

error C3520: 'args' : parameter pack must be expanded in this context (test.cpp)

我应该如何正确“解包”args 参数包?我认为这就是省略号的目的。

【问题讨论】:

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


    【解决方案1】:

    您可以在参数包扩展周围添加花括号:

    template<typename T, class ... Args>
    T Mean(Args ... args)
    {
        int numArgs = sizeof...(args);
        if (numArgs == 0)
            return T();           // If there are no arguments, just return the default value of that type
    
        T total;
        for (auto value : {args...})
        {
            total += value;
        }
    
        return total / numArgs;   // Simple arithmetic average (sum divided by total)
    }
    

    这应该会创建一个std::initializer_list,然后您可以在其上使用基于范围的 for 循环。

    【讨论】:

    • 我无法弄清楚如何实际调用此代码以实际工作;当我这样做时:双重测试=平均值(5、10、50);我收到错误 C2672:“平均值”:找不到匹配的重载函数 错误 C2783:“T 平均值(Args ...)”:无法推断“T”的模板参数
    • @sonictk 尝试 double test = Mean&lt;int&gt;(5, 10, 50); 代替,有一些方法可以修改此函数,因此您不必指定结果类型,但这取决于您的用例(您可能总是需要整数或总是双打或总是第一个参数的类型等),我觉得这是最通用的方法,因为它把选择权留给了函数用户。
    【解决方案2】:

    @Drax 的答案可能是这里的方法。或者,您可以递归地执行此操作,自动推断返回类型,以便您可以混合类型。缺点是你的代码需要更多的编译时间,所以下面的答案更多的是可变参数模板递归的练习。代码:

    #include <iostream>
    
    using namespace std;
    
    template<typename T>
    T Mean(T head)
    {
        return head;
    }
    
    template<typename T, class ... Args>
    T Mean(T head, Args... args)
    {
        auto N = sizeof...(Args);
        return (head + (N)*Mean(args...)) / (N + 1);  
    }
    
    int main(void)
    {
        cout << Mean((double)1, (int)2, (float)4) << endl; // (double) 2.3333...
    }
    

    或者,使用包装器,

    #include <iostream>
    
    using namespace std;
    
    template<typename T>
    T Mean_wrapper(T head)
    {
        return head;
    }
    
    // return type is the type of the head of param list
    template<typename T, class ... Args>
    T Mean_wrapper(T head, Args... args) 
    {
        return head + Mean_wrapper(args...);   
    }
    
    template<typename T, class ... Args>
    T Mean(T head, Args... args)
    {
        return Mean_wrapper(head, args...) / (sizeof...(args) + 1);
    }
    
    int main(void)
    {
        cout << Mean((double)10, (int)20, (float)30) << endl; // (double) 20
    
    }
    

    【讨论】:

    • @DieterLücking 我认为您在浏览器缓存中有一段以前未编辑的代码,调用是Mean((double)1, (int)2, (float)4)
    • 啊,好的 :) 谢谢,那是我的错字,但为什么我看不到你编辑了它?
    • @DieterLücking,好的,我明白了,我首先认为你的意思是我的代码中有错字。
    【解决方案3】:

    请注意,您可以将包展开到标准容器中并使用常用算法来获取结果。

    template <typename T, class... Args, std::size_t N = sizeof...(Args)>
    T Mean(Args... args) {
      std::array<T, N> arr = {args...};
      if (N > 0) return std::accumulate(std::begin(arr), std::end(arr), T{}) / N;
      return T{};
    }
    

    【讨论】:

    • 虽然这不支持不同类型的输入,但它非常有吸引力......除了一个问题。如果 T 是整数,您将得到整数除法。虽然 OP 的代码确实也出现了这个问题,但如果有人要复制您的代码并普遍使用它,他们会感到不快。所以,我建议你做一些事情,比如将 N 转换为 double。
    【解决方案4】:

    递归并考虑参数类型:

    #include <iostream>
    #include <type_traits>
    
    namespace Detail {
    
        template <typename T, typename ... Args>
        struct Sum;
    
        template <typename T>
        struct Sum<T> {
            typedef T type;
            static type apply(T value) { return value; }
        };
    
        template <typename T, typename ... Args>
        struct Sum {
            typedef decltype(std::declval<T>() + std::declval<typename Sum<Args...>::type>()) type;
            static type apply(T a, Args ...args) {
                return a + Sum<Args...>::apply(args...);
            }
        };
    } // namespace Detail
    
    template <typename ... Args>
    typename Detail::Sum<Args...>::type sum(Args ... args) {
        return Detail::Sum<Args...>::apply(args...);
    }
    
    template <typename ... Args>
    typename Detail::Sum<Args...>::type mean(Args ... args) {
        return Detail::Sum<Args...>::apply(args...) / sizeof...(Args);
    }
    
    
    int main()
    {
        // 2.5 / 2
        std::cout << mean(int(1), double(1.5)) << '\n';
        return 0;
    }
    

    【讨论】:

      【解决方案5】:

      如果您可以使用 C++17,这也可以:

      template<typename T, class ... Args> T mean(Args ... args)
      {
          return (static_cast<T>(0) + ... + args)/sizeof...(args);
      };
      

      灵感来自:https://stackoverflow.com/a/52352776

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-02-06
        • 2017-09-26
        • 2015-12-26
        • 2021-02-14
        • 2017-03-03
        • 1970-01-01
        • 1970-01-01
        • 2020-10-31
        相关资源
        最近更新 更多