Is there any "tool" in standard, that for such template function:
template<typename FirstArg, typename... Args>
auto average(FirstArg &&firstArg_, Args&&... args_)
{
// example:
std::widest_type_t<FirstArg> sum;
sum += std::forward<FirstArg>(firstArg_);
sum += (... + std::forward<Args>(args_)); // unfold
sum /= (sizeof...(Args) + 1);
return sum;
}
Lets say, that every parameter type in this template is the same. For example: average of n std::int32_t
's. I used imaginary widest_type_t
to visualize the usage. Average calculation needs to sum every parameter, therefore, to avoid (or minimize as best as I can) overflows I need to use maximal-width type possible. Example:
char
-> std::intmax_t
std::uint16_t
-> std::uintmax_t
float
-> long double
(or some other type, decided by implementation)Surely, I can write this myself, but having something like this in the standard would be nice.
Edit:
I could use moving average, however this function will be used only on small number of parameters (typically 2-8), but types I use are easily "overflowable".
Edit 2:
I also know, that for larger amounts of parameters, it would be better to use any type of array.
Using the widest type doesn't guarantee a lack of overflow (and can still cut out fractional values when dividing), but you can extend promotion rules to do this:
template<typename T>
struct widest_type {
static constexpr auto calculate() {
if constexpr (std::is_floating_point_v<T>) {
using LongDouble = long double;
return LongDouble{};
} else if constexpr (std::is_signed_v<T>) {
return std::intmax_t{};
} else if constexpr (std::is_unsigned_v<T>) {
return std::uintmax_t{};
} else {
return std::declval<T>();
}
}
using type = decltype(calculate());
};
template<typename T>
using widest_type_t = typename widest_type<T>::type;
template<typename FirstArg, typename... Args>
auto average(FirstArg &&firstArg_, Args&&... args_)
{
using Common = std::common_type_t<FirstArg, Args...>;
widest_type_t<Common> sum;
sum += std::forward<FirstArg>(firstArg_);
sum += (... + std::forward<Args>(args_)); // unfold
sum /= sizeof...(args_) + 1;
return sum;
}
If if constexpr
isn't available, then std::conditional
will do the trick. Likewise, std::is_foo<T>{}
works in place of std::is_foo_v<T>
.
I chose to keep the type trait limited to a single type since std::common_type
already does a reasonable job figuring out how to combine types. You'll notice I use that and pass the result into widest_type
.