3

I am trying to write a function to determine the average of an arbitrary number of arguments, all of which have the same type. For learning purposes I am trying to do this using a variadic-templated function.

This is what I have so far:

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)
}

When I try to compile this (using MS Visual Studio 2013) I get the following compilation error:

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

How should I properly "unpack" the args parameter pack? I thought that was the purpose of the ellipses.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
Cory Kramer
  • 114,268
  • 16
  • 167
  • 218

5 Answers5

6

You can add curly braces around your parameter pack expansion:

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)
}

This should create an std::initializer_list on which you can then use range-based for loops.

Drax
  • 12,682
  • 7
  • 45
  • 85
  • I couldn't figure out how to actually call this code to actually work; when I do: double test = Mean(5, 10, 50); I get error C2672: 'Mean': no matching overloaded function found error C2783: 'T Mean(Args...)': could not deduce template argument for 'T' – sonictk May 06 '18 at 07:43
  • 1
    @sonictk try `double test = Mean(5, 10, 50);` instead, there are ways you can modify this function so you don't have to specify the result type but it depends on your use case (you could always want integers or always doubles or always the type of the first parameter etc.), i feel like this is the most general purpose way to do it as it leaves the choice to the function user. – Drax May 09 '18 at 08:37
2

@Drax's answer is probably the way to go here. Alternatively, you can do it recursively, having the return type auto-deduced, so you can mix types. The disadvantage is that your code will take more to compile, so the answer below is more of an exercise in variadic templates recursion. Code:

#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...
}

or, with a wrapper,

#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

}
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • @DieterLücking I think you have a previously un-edited piece of code in the browser cache, the call is `Mean((double)1, (int)2, (float)4) ` – vsoftco Jul 29 '14 at 18:42
  • Ahh ok :) thanks, it was a typo on my part then, but why cannot I see that you edited it? – vsoftco Jul 29 '14 at 18:56
  • @DieterLücking, ok I got it, I thought first that you meant I have a typo in my code. – vsoftco Jul 29 '14 at 19:22
1

Note that you can expand the pack into a standard container and use the usual algorithms to get the result.

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{};
}
Jiří Pospíšil
  • 14,296
  • 2
  • 41
  • 52
  • 1
    While this does not support inputs of different types, it's very attractive... except for one problem. You will get integer division if T is integral. While it's true that OP's code also exhibited this problem, if anyone were to copy your code and use it generally they would be unpleasently surprised. So, I suggest you do something like casting N as double. – einpoklum Aug 27 '16 at 08:37
1

Recursively and considering argument types:

#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

If you can use C++17, this would also do the job:

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

Inspired by: https://stackoverflow.com/a/52352776

ferdymercury
  • 698
  • 4
  • 15