【问题标题】:Determining the "optimal" common numeric type in a template parameter pack确定模板参数包中的“最佳”通用数值类型
【发布时间】:2013-08-19 11:30:24
【问题描述】:

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

  1. 最小尺寸,
  2. 不损失精度,并且
  3. 将参数包中的任何类型转换为这种“理想”的通用类型时,不会有上溢/下溢的风险吗?

可变参数模板 (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 不能保存 @987654331 @ 没有下溢的风险。

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

(实现可能是 data model 特定的,但你明白了。)

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

【问题讨论】:

  • std::common_type 有什么问题?
  • @DanielKO:我对所有 5 个案例都尝试了std::common_type,它适用于 1-3,因为它们是非常简单的案例,但是对于案例 [4],“返回”unsigned int(而不是signed long) 和 unsigned long 用于案例 [5](而不是比 long 更宽的有符号整数类型)。这是因为这些类型可以转换为无符号对应,但并非没有潜在的下溢,best_common_numeric_type 必须避免这种下溢。
  • &lt;signed long long, unsigned long long&gt; 怎么样?有些组合没有结果。还有&lt;long long, double&gt;。 (还要注意 long 在某些系统上是 32 位的,所以 #4 可能是错误的)
  • @MooningDuck:正确。在这些情况下,模板只能推断出可能的“最佳”类型(并且可能会生成警告或 static_assert 失败,这些细节可以为了回答这个问题而被忽略)。对于您的第二点,我在问题中指出,实现可能是“数据模型”特定的,但也可以忽略(即可以假设数据模型)。与数据模型无关的解决方案可能是使用 signed_int_with_size_gt&lt;...&gt;::type 等模板,但这超出了本问题的范围。
  • @MikeTusar:如果你可以解决两个问题,那么扩展它来解决任何数字都是微不足道的。

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


【解决方案1】:

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

【讨论】:

  • 我喜欢。 +1 我希望你能完成/恢复那个答案
  • @sehe:我同意,这个解决方案似乎尽可能简单和干净。我认为因为这是一个完整 解决方案(处理浮点),相当简单,并且仅使用标准库,我认为它应该成为公认的(“最佳”)答案。我希望 SO 允许接受两个答案,因为作为更基于 MPL 的解决方案,您的答案也非常出色且非常有趣。
  • 优秀。而且非常简单。
【解决方案2】:

您可以使用 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)));
}

【讨论】:

  • 很好的解决方案和 MPL 的使用。
  • 请参阅 leonid 回答下的评论。在您的答案之间做出决定很难,因为我认为您的答案更“有趣”,但最终他的答案是完整且简单的。我当然会为您保留 +1,因为它也是出色且有趣的基于 MPL 的方法。
  • 遗憾的是,由于使用了类型别名,这不适用于 C++03。
  • @abergmeier C++0x 不是一个东西。这段代码明显是 c++11(问题被标记为 c++11variadic-templates 等)这里还有其他解决 C++03 的答案(这就是你想要的)
【解决方案3】:

注意:不知何故,我一直认为你需要 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

【讨论】:

  • 使用 C++11,您可以使 best_common_numeric_type2 和朋友成为更好的可变参数模板,您可以使用 using,这在这种情况下只是语法。除此之外,C++11 似乎对这种情况没有多大帮助。
  • 这很好,唯一的问题是它不符合所述标准的第一部分(尽可能具有最小的sizeof)。您可以看到问题中还说明了每个测试用例示例的结果 ::type 应该是什么。正如您所说,这也可以很好地使用 C++11 可变参数模板进行清理。抱歉,我没有说得更清楚,尽管我在“C++11 中实现这一目标的最佳方式可能是什么?”中确实说明了这一点。再次感谢您的帮助和有趣的讨论! +1
  • 顺便说一句-使用boost::mpl查看上面的解决方案,它很干净。
  • @MikeTusar:它干净的部分原因是它不处理浮点类型。做最小尺寸是相当困难的,我把this comment 误解为允许我做的事情。
  • 浮点情况很简单,因为如果 any 类型是 FP 类型,那么 ::type 就是包中精度最高的 FP 类型。整体案例更难,sehe 的解决方案甚至通过使用boost::int_t&lt;bin_digits + 1&gt;::least(或boost::uint_t,取决于标牌)以数据模型中立的方式解决了它,这是一个额外的好处。
猜你喜欢
  • 2020-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多