Determining the “optimal” common numeric type in a template parameter pack

后端 未结 3 550
清酒与你
清酒与你 2021-01-05 05:02

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, a
相关标签:
3条回答
  • 2021-01-05 05:24

    Note: somehow I got it stuck in my head that you needed C++03 for this. This can be simplified for C++11. This also does NOT pick the smallest size.

    There's nothing standard for this to my knowledge, but it can be done: http://coliru.stacked-crooked.com/view?id=c6aa42345f91ab51d745d56573b15a04-4f34a5fd633ef9f45cb08f8e23efae0a

    First the "thinker" structs.

    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;};
    

    Then the base cases:

    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.
    

    Then the joiners:

    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;
    };
    

    And finally a test:

    #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());
    }
    

    Results:

    long double
    ull
    ll
    ll
    ll
    long double
    
    0 讨论(0)
  • 2021-01-05 05:25

    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)));
    }
    
    0 讨论(0)
  • 2021-01-05 05:30

    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;
    };
    
    0 讨论(0)
提交回复
热议问题