What is the best way to determine a common numeric type in a template parameter pack with:
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?
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;
};
You could use Boost Integer to select the proper cases.
Ignoring for a moment the cases on non-integral element types, here's a quick test of the proposed cases (GCC doesn't have int128_t
as it appears):
Live on Coliru
#include <boost/mpl/vector.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/max_element.hpp>
#include <boost/integer.hpp>
#include <limits>
using namespace boost;
namespace best_fit_
{
// wrappers around Boost Integer http://www.boost.org/doc/libs/1_54_0/libs/integer/doc/html/boost_integer/integer.html#boost_integer.integer.sized
template <bool is_signed, int bin_digits> struct select_int;
template <int bin_digits> struct select_int<true, bin_digits> {
using type = typename boost::int_t<bin_digits + 1>::least;
};
template <int bin_digits> struct select_int<false, bin_digits> {
using type = typename boost::uint_t<bin_digits>::least;
};
// query helper
struct digits {
template <typename I> using apply = mpl::int_<std::numeric_limits<I>::digits>;
};
}
template <typename... I>
struct best_common_integral
{
private:
using Ints = mpl::vector<I...>;
using Bits = typename mpl::transform<Ints, best_fit_::digits>::type;
template <typename J>
struct is_signed { static constexpr bool value = std::numeric_limits<J>::is_signed; };
using max = typename mpl::deref<typename mpl::max_element<Bits>::type>::type;
// sigh, there is no `mpl::any`, AFAICT
using sign = typename mpl::fold<
Ints,
mpl::bool_<false>,
mpl::if_<is_signed<mpl::_2>, mpl::bool_<true>, mpl::_1>
>::type;
public:
using type = typename best_fit_::select_int<sign::value, max::value>::type;
};
#include <typeinfo>
#include <iostream>
#include <cassert>
int main()
{
using case1 = best_common_integral<long, unsigned long, float, double, int>;
using case2 = best_common_integral<unsigned int, unsigned long>;
using case3 = best_common_integral<signed int, signed long>;
using case4 = best_common_integral<signed int, unsigned int>;
using case5 = best_common_integral<signed int, unsigned long>;
//assert(typeid(case1::type) == typeid(double));
assert(typeid(case2::type) == typeid(unsigned long));
assert(typeid(case3::type) == typeid(signed long));
assert(typeid(case4::type) == typeid(signed long));
//assert(typeid(case5::type) == typeid(int128_t (maybe)));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With