Search code examples
c++variadic-functionstype-traitsfold-expression

Widest possible type similar to a given one - C++


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.


Solution

  • 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.