4

I am trying to understand how to properly implement a function to calculate the mathematical sample mean, with two initial required characteristics:

1) using variadic arguments.

2) not using two functions to do the job, that is, not using a caller function and then a second function that actually does the calculation.

3) function should be as generic as possible

I am very aware that a pretty similar question has been already asked: Calculate the average of several values using a variadic-template function However, while the accepted answer to that question seems to have taught the OP how to do the small part he didn't know, it presents a code that is actually wrong and does not compile.

So, my own first attempt was along these lines:

template <class... Args>
double mean(const Args & ... args)
{
    auto total = 0;
    for (auto value : { ...args })
    {
        total += value;
    }
    return (double)total / sizeof...(args);
}

The problem here is that in the line auto total = 0; the compile naturally can't automatically identify the type that object total should have.

Then, my second attempt:

template <class T, class... Args>
T mean(const T &t, const Args & ... args)
{
    T total = 0;
    for (auto value : { args... })
    {
        total += value;
    }
    return (T)(total / sizeof...(args));
}

That version the following problem. It does not work if the caller calls the function with arguments of mixed type, like in mean(1, 2.5), where the first arguments is automatically detected as an int and the second is detected as a double.

I was able to solve that by doing the following:

template <class T, class... Args>
T mean(const T &t, const Args & ... args)
{
    size_t argsSize = sizeof...(args);
    T total = t;
    T arg_array[] = { args... };
    for (size_t i = 0; i< argsSize; i++)
    {
        total += (T)arg_array[i];
    }
    return (T)(total / argsSize) ;
}

This one works even when passed arguments are of different types (of course, provided that such types can be converted to T). The problem now, however, is that the function only works with a minimum of two arguments. If it is called like in mean(3.14), while it should return 3.14, it actually raises an error because T arg_array[] = { args... } can't be compiled because it is impossible to have a static array be created with size 0. Sure, I could substitute it for a dynamic array, but that would make me have to do one memory allocation and one memory deallocation every time that the function is called - which is an unacceptable waste.

So, what would be the correct way to implement such a function that avoids the mentioned problems and that follows my two initial conditions?

max66
  • 65,235
  • 10
  • 71
  • 111
Juddy
  • 329
  • 1
  • 2
  • 7
  • You need to get into the way of thinking recursively. The mean of one number is simply the value, which is your termination condition. The mean of N is the mean of the next N-1, times N-1, plus the top value, divided by N. – Malcolm McLean Jun 07 '17 at 12:27

3 Answers3

4

Use std::common_type_t:

template<class... Args> constexpr auto mean(Args... args) {
    std::common_type_t<Args...> total(0);
    for(auto value : {std::common_type_t<Args...>(args)...}) total += value;
    return total/sizeof...(args);
}
Diego91b
  • 121
  • 4
2

Use fold expressions in C++17

template<typename... Args>
constexpr decltype(auto) mean(Args&&... args)
{
    return (... + std::forward<Args>(args)) / sizeof...(Args);
}

The std::forward is there for some bignum types that supports moving.

Before C++17, manual folding is required, but that will require overloading mean for a base case, which seems like not what you want.

Passer By
  • 19,325
  • 6
  • 49
  • 96
  • nice (+1), but you should precise that fold expressions are available starting from C++17. – max66 Jun 07 '17 at 12:00
0

To solve the problem of "the function only works with a minimum of two arguments" you can create a simple type traits to extract the first type

template <typename T0, typename ...>
struct firstType
 { using type = T0; };

or, if you prefer (and, IMHO, is better), follow the std::common_type_t suggestion from Diego91b (+1).

You can write mean(), in C++11, as follows

template <typename ... Args>
typename firstType<Args...>::type mean (Args const & ... args)
// or typename std::common_type<Args...>::type mean (Args const & ... args)
 {
   using unused = int[];

   typename firstType<Args...>::type  total { 0 };
   // or typename std::common_type<Args...>::type  total { 0 };

   (void)unused { (total += args, 0)... };

   return total / sizeof...(args);
 }

In C++14 can be semplified (and transformed in constexpr) as follows

template <typename ... Args>
constexpr auto mean (Args const & ... args)
 {
   using unused = int[];

   //typename firstType<Args...>::type  total { 0 };
   std::common_type_t<Args...>  total { 0 };

   (void)unused { (total += args, 0)... };

   return total / sizeof...(args);
 }

In C++17 you can use fold expressions (as suggested by Passer By) and become realy easy

template <typename ... Args>
constexpr typename firstType<Args...>::type mean (Args const & ... args)
// or constexpr auto mean (Args const & ... args)
{
    return (... + args) / sizeof...(Args);
}

The following is a full compilable C++11 example

#include <iostream>
#include <type_traits>

template <typename T0, typename ...>
struct firstType
 { using type = T0; };

template <typename ... Args>
typename firstType<Args...>::type mean (Args const & ... args)
// or typename std::common_type<Args...>::type mean (Args const & ... args)
 {
   using unused = int[];

   typename firstType<Args...>::type  total { 0 };
   // or typename std::common_type<Args...>::type  total { 0 };

   (void)unused { (total += args, 0)... };

   return total / sizeof...(args);
 }

int main()
 {
   std::cout << mean(2, 5.5) << std::endl; // print 3 (3.75 with common_type)
   std::cout << mean(5.5, 2) << std::endl; // print 3.75
   std::cout << mean(2) << std::endl;      // print 2
   // std::cout << mean() << std::endl; compilation error
 }

-- EDIT --

The OP ask

do you know why exactly your solution gives 3 while using common_type gives 3.75 when calling mean(2, 5.5)?

Sure.

When you call mean(2, 5.5) using firstType, the type of the first argument is the type of 2, that is int. So total is declared as int and when 5.5 is added to total, is converted to int, so become 5, ad added to 2. The total become 7 and divided by 2, so (integer division) become 3.

On the countrary, if you call mean(5.5, 2) the firstType is the type of 5.5 that is double. So total become a double, add 2 and 5.5 and obtain 7.5. The last division (7.5 / 2) is a double division that give 3.75.

When you call mean(2, 5.5) (or mean(5.5, 2)) using std::common_type_t, the common type between int and double is double; so total is defined as double and the function return 7.5 / 2 that is 3.75.

-- EDIT 2 --

The OP ask

Is there a particular reason why using unused has to be specifically assigned an int[]? I tried changing it to typename std::common_type<Args...>::type, which seems to me would make the function more generic, but then it does not compile. Also, it does not work with double[].

The unused variable is ... unused. Take in count that is declared only to permit, in it's initialization, to add in total the values of args, unpacking they.

The trick is the comma: observe

(total += args, 0)...

The comma discard the left value (that is computed, so total is modified, but discarted) and return the right value. That is zero. An integer zero. And for this reason is int[].

The final result is that unused is initialized with { 0, 0, 0, ... }.

There is no need to transform it in something of different type; it's only a trick to use the args pack.

If you want, you can do it (by example) float; as follows

using unused = float[];

(void)unused { (total += args, 0.0f)... };

(observe the 0.0f) but... why we should do it?

max66
  • 65,235
  • 10
  • 71
  • 111
  • Many thanks, that answer is fantastic. Quick and last follow up, for the sake of learning: do you know why exactly your solution gives `3` while using `common_type` gives `3.75` when calling `mean(2, 5.5)`? – Juddy Jun 09 '17 at 15:21
  • @Juddy - answer improved; hope this helps. – max66 Jun 09 '17 at 17:18
  • Oh, I see. What is puzzling to me then is that the common type between `int` and `double` is `double`. But that is a matter for further study and eventually, if I can't understand, another different question. Thanks a lot for the answers – Juddy Jun 09 '17 at 17:20
  • @Juddy - anser improved again; I'm not good in explaining my code but I hope you can understand the comma trick in `unused` because it's very useful (before C++17) in many variadic arguments uses. – max66 Jun 09 '17 at 20:18