Определение" оптимального " общего числового типа в пакете параметров шаблона

каков наилучший способ определить общие числовой тип в пакете параметров шаблона с:

  1. самый маленький размер,
  2. нет потери точности, и
  3. отсутствие риска переполнения / underflow при преобразовании любого типа в пакете параметров в этот "идеальный" общий тип?

шаблон variadic (best_common_numeric_type) может использоваться так:

template<typename... NumericTypes>
auto some_numeric_func(const NumericTypes&...)
-> typename best_common_numeric_type<NumericTypes...>::type;

и есть примеры, как следующий:

[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)

так, в случае [4] например, ::type должен быть signed long С signed int не смог удержать unsigned int без риска переполнения, и наоборот unsigned int не смог удержать signed int без риска underflow.

то же самое относится к [5], за исключением теперь signed long больше не достаточно, так как он не мог удерживать unsigned long без риска перелива.

(реализация может быть модель данных специфический, но вы получить идею.)

Итак, что может быть лучшим способом в C++11 для достижения этого?

3 ответов


Я немного опоздал на вечеринку, вот мое решение без 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;
};

Вы можете использовать Boost Integer для выбора правильных случаев.

игнорируя на мгновение случаи на нецелочисленных типах элементов, вот быстрый тест предлагаемых случаев (GCC не имеет int128_t как оно выглядит):

жить на 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)));
}

Примечание: как-то я застрял в моей голове, что вам нужен C++03 для этого. Это можно упростить для C++11. Это также не выбирает наименьший размер.

нет ничего стандартного для этого, насколько мне известно, но это можно сделать: http://coliru.stacked-crooked.com/view?id=c6aa42345f91ab51d745d56573b15a04-4f34a5fd633ef9f45cb08f8e23efae0a

сначала структуры "мыслителя".

template<bool isfloat, bool negative> struct best_numeric_type 
{typedef long double type;};
template<> struct best_numeric_type<false, true> 
{typedef long long type;};
template<> struct best_numeric_type<false, false> 
{typedef unsigned long long type;};

затем база случаи:

template<class T> struct best_common_numeric_type1 {
    static const bool isfloat=false;
    static const bool negative=false;
    typedef typename best_numeric_type<isfloat, negative>::type type;
};

template<> struct best_common_numeric_type1<char> {
    static const bool isfloat=false;
    static const bool negative=true;
    typedef typename best_numeric_type<isfloat, negative>::type type;
};//copy-paste for signed char, short, int, long, and long long.

template<> struct best_common_numeric_type1<float> {
    static const bool isfloat=true;
    static const bool negative=false;
    typedef typename best_numeric_type<isfloat, negative>::type type;
};//copy-paste for double and long double.

затем Столяров:

template<class First, class Second>
struct best_common_numeric_type2 {
    static const bool isfloat = best_common_numeric_type1<First>::isfloat |  best_common_numeric_type1<Second>::isfloat;
    static const bool negative = best_common_numeric_type1<First>::negative |  best_common_numeric_type1<Second>::negative;
    typedef typename best_numeric_type<isfloat, negative>::type type;
};
template<class First, class Second, class Third>
struct best_common_numeric_type3 {
    static const bool isfloat = best_common_numeric_type2<First, Second>::isfloat |  best_common_numeric_type1<Third>::isfloat;
    static const bool negative = best_common_numeric_type2<First, Second>::negative |  best_common_numeric_type1<Third>::negative;
    typedef typename best_numeric_type<isfloat, negative>::type type;
};
template<class First, class Second, class Third, class Fourth>
struct best_common_numeric_type4 {
    static const bool isfloat = best_common_numeric_type3<First, Second, Third>::isfloat |  best_common_numeric_type1<Fourth>::isfloat;
    static const bool negative = best_common_numeric_type3<First, Second, Third>::negative |  best_common_numeric_type1<Fourth>::negative;
    typedef typename best_numeric_type<isfloat, negative>::type type;
};
template<class First, class Second, class Third, class Fourth, class Fifth>
struct best_common_numeric_type5 {
    static const bool isfloat = best_common_numeric_type4<First, Second, Third, Fourth>::isfloat |  best_common_numeric_type1<Fifth>::isfloat;
    static const bool negative = best_common_numeric_type4<First, Second, Third, Fourth>::negative |  best_common_numeric_type1<Fifth>::negative;
    typedef typename best_numeric_type<isfloat, negative>::type type;
};

и, наконец, тест:

#include <typeinfo>
#include <iostream>       
void printer(long double) {std::cout << "long double\n";}
void printer(unsigned long long) {std::cout << "ull\n";}
void printer(long long) {std::cout << "ll\n";}
void printer(...) {std::cout << "else\n";}

int main() {
    printer(best_common_numeric_type5<long, unsigned long, float, double, int>::type());
    printer(best_common_numeric_type2<unsigned int, unsigned long>::type());
    printer(best_common_numeric_type2<signed int, signed long>::type());
    printer(best_common_numeric_type2<signed int, unsigned int>::type());
    printer(best_common_numeric_type2<signed int, unsigned long>::type());
    printer(best_common_numeric_type2<float, char>::type());
}

результаты:

long double
ull
ll
ll
ll
long double