Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determining the "optimal" common numeric type in a template parameter pack

What is the best way to determine a common numeric type in a template parameter pack with:

  1. the smallest size,
  2. no loss of precision, and
  3. 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?

like image 264
MikeTusar Avatar asked Aug 17 '13 00:08

MikeTusar


2 Answers

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;
};
like image 154
Leonid Volnitsky Avatar answered Oct 28 '22 07:10

Leonid Volnitsky


You could use Boost Integer to select the proper cases.

  • http://www.boost.org/doc/libs/1_54_0/libs/integer/doc/html/boost_integer/integer.html#boost_integer.integer.sized

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)));
}
like image 6
sehe Avatar answered Oct 28 '22 06:10

sehe