确定模板参数包中的“最佳”通用数字类型

时间:2013-08-17 00:11:42

标签: c++ templates c++11 variadic-templates template-meta-programming

在模板参数包中使用以下方法确定常用数字类型的最佳方法是:

  1. 最小尺寸,
  2. 没有精度损失,
  3. 将参数包中的任何类型转换为此“理想”常见类型时,是否存在溢出/下溢的风险?
  4. 可变参数模板(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 {1}}无法保留signed int而没有下溢风险。

    同样适用于[5],但现在signed long不再足够,因为它无法保持unsigned long而没有溢出的风险。

    (实施可能是data model具体的,但你明白了。)

    那么在C ++ 11中实现这一目标的最佳方式是什么呢?

3 个答案:

答案 0 :(得分:6)

您可以使用Boost Integer选择合适的案例。

暂时忽略非整数元素类型的情况,这里是对建议案例的快速测试(GCC没有出现int128_t):

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

答案 1 :(得分:6)

我参加派对有点晚了, 这是我没有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;
};

答案 2 :(得分:4)

注意:不知何故,我觉得你需要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