Determining the "optimal" common numeric type in a template parameter pack
c++, c++11, template-meta-programming, templates, variadic-templates
Solution
I am a little bit late to the party, here is my solution without Boost:
#include <type_traits>
#include <cstdint>
template<class I, bool Signed> struct mk_signed { typedef I type; };
template<> struct mk_signed<uint8_t , true> { typedef int16_t type; };
template<> struct mk_signed<uint16_t, true> { typedef int32_t type; };
template<> struct mk_signed<uint32_t, true> { typedef int64_t type; };
template<> struct mk_signed<uint64_t, true> { typedef int64_t type; };
template <typename... Ts> struct best_common_numeric_type;
template <typename T> struct best_common_numeric_type<T> { typedef T type; };
template <typename T, typename... Ts>
struct best_common_numeric_type<T, Ts...> {
typedef typename best_common_numeric_type<Ts...>::type TS;
typedef typename std::conditional < (sizeof(T) > sizeof(TS)), T, TS>::type bigger_integral;
constexpr static bool fp = std::is_floating_point<T>::value || std::is_floating_point<TS>::value;
constexpr static bool have_signed = !fp && (std::is_signed<T>::value || std::is_signed<TS>::value);
typedef typename std::conditional <
fp,
typename std::common_type<T,TS>::type,
typename mk_signed<bigger_integral,have_signed>::type
>::type type;
};
Problem
What is the best way to determine a common numeric type in a template parameter pack with: - the smallest size, - no loss of precision, and - no risk of overflow/underflow when converting any type in the parameter pack to this "ideal" common type? The variadic template (`best_common_numeric_type`) could be used like so: ``` template<typename... NumericTypes> auto some_numeric_func(const NumericTypes&...) -> typename best_common_numeric_type<NumericTypes...>::type; ``` And have instantiations like the following: ``` [1] best_common_numeric_type<long, unsigned long, float, double, int>::type = double [2] best_common_numeric_type<unsigned int, unsigned long>::type = unsigned long [3] best_common_numeric_type<signed int, signed long>::type = signed long [4] best_common_numeric_type<signed int, unsigned int>::type = signed long [5] best_common_numeric_type<signed int, unsigned long>::type = int128_t (maybe) ``` So in case [4] for example, `::type` would have to be `signed long`, since `signed int` could not hold an `unsigned int` without risk of overflow, and conversely `unsigned int` could not hold a `signed int` without risk of underflow. The same applies in [5], except now a `signed long` is no longer sufficient since it could not hold the `unsigned long` without risk of overflow. (The implementation might be data model specific, but you get the idea.) So what might be the best way in C++11 to achieve this?