使用可变模板函数计算多个值的平均值

3
我正在尝试编写一个函数来确定任意数量参数的平均值,其中所有参数都具有相同的类型。出于学习目的,我正在尝试使用可变模板函数来实现。
以下是我的代码:
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参数包?我认为这就是省略号的目的。
5个回答

6
你可以在你的参数包展开周围添加花括号:
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,您可以在其上使用范围-based for 循环。


我无法弄清如何调用这段代码使其正常工作;当我执行以下操作时:double test = Mean(5, 10, 50); 我得到错误C2672:“Mean”:找不到匹配的重载函数和错误C2783:“T Mean(Args ...)”:无法推断出“T”的模板参数。 - sonictk
1
@sonictk 尝试使用double test = Mean<int>(5, 10, 50);,这样你就不需要指定结果类型了。你可以修改这个函数的方式,使其不必指定结果类型,但这取决于你的用例(你可能总是想要整数、双精度浮点数或者总是想要第一个参数的类型等等)。我觉得这是最通用的方法,因为它将选择权留给了函数的使用者。 - Drax

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) - vsoftco
啊,好的 :) 谢谢,那就是我写错了,但是为什么我看不到你编辑过呢? - vsoftco
@DieterLücking,好的,我明白了,一开始我以为你是说我的代码有错别字。 - vsoftco

1
请注意,您可以将包扩展到标准容器中,并使用常规算法来获取结果。
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{};
}

1
虽然这个方法不支持不同类型的输入,但它非常有吸引力...除了一个问题。如果T是整数,你会得到整数除法。虽然OP的代码也存在这个问题,但如果有人复制你的代码并通常使用它,他们会感到惊讶。因此,我建议你将N转换为double之类的操作。 - einpoklum

1
递归并考虑参数类型:
#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;
}

0
如果您能使用C++17,这也可以完成工作:
template<typename T, class ... Args> T mean(Args ... args)
{
    return (static_cast<T>(0) + ... + args)/sizeof...(args);
};

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接