自动选择一个足够容纳特定数字的变量类型

59

在C ++中,是否有一种方式可以定义一个类型,该类型足够大,以至于最多只能容纳特定数量的数据,可能需要使用一些巧妙的模板代码。例如,我想能够编写:

Integer<10000>::type dataItem;
并且将该类型解析为足够大以容纳指定值的最小类型?
背景:我需要使用一个脚本从外部数据文件生成一些变量定义。我想我可以让脚本查看这些值,然后根据值使用 uint8_tuint16_tuint32_t 等等,但似乎更加优雅的做法是将大小构建到生成的 C++ 代码中。
我无法想出一种可以实现这一点的模板方式,但了解 C++ 模板,我相信一定有方法。有什么想法吗?

1
你的意思是在运行时 dataItem 不应超过 10000 吗? - iammilind
11
我认为JohnB的意思是,例如Integer<10000>::type应该解析为uint16_t,因为你不能将10000存储在uint8_t中,但可以将其存储在uint16_t中。 - In silico
1
@JohnB:既然你正在自己解析文件,为什么不让解析器检查最小范围并写出 uint8_tuint16_t 等呢?例如,如果你发现需要写出的值是 10000,那么解析器将会计算出还需要写出 uint16_t - In silico
1
@In silico 是的,我可以这样做,这可能是我要做的。只是这样做看起来很优雅,所以我想问一下是否可能。 - jcoder
1
@Lie Ryan:你在哪里找到的Python语法可以帮助C++模板? - Zoey
显示剩余9条评论
15个回答

63

Boost.Integer已经提供了整数类型选择的功能:Integer Type Selection

boost::int_max_value_t<V>::least

这是一个最小的有符号整数类型,可以容纳范围在0到V之间的所有值。参数应该是一个正数。

boost::uint_value_t<V>::least

最小的内置无符号整数类型,它可以容纳所有正值,包括V及以下。该参数应为正数。


哈,原来如此。我已经在项目中使用了boost库,所以我想我就直接用它了!虽然回答问题的答案都很好,也很有教育意义 :) - jcoder
要使用这些类型选择模板,需要包含#include <boost/integer.hpp> - Kai Petzke

49

当然可以。以下是所需材料。我们从我最喜欢的两个元函数开始:

template<uint64_t N>
struct constant
{
    enum { value = N };
};

template<typename T>
struct return_
{
    typedef T type;
};

接下来,一个计算存储数字所需的位数的元函数:

template<uint64_t N>
struct bitcount : constant<1 + bitcount<(N>>1)>::value> {};

template<>
struct bitcount<0> : constant<1> {};

template<>
struct bitcount<1> : constant<1> {};

然后,一个计算字节数的元函数:

template<uint64_t N>
struct bytecount : constant<((bitcount<N>::value + 7) >> 3)> {};

然后,一个元函数会根据给定的字节数返回最小类型:

template<uint64_t N>
struct bytetype : return_<uint64_t> {};

template<>
struct bytetype<4> : return_<uint32_t> {};

template<>
struct bytetype<3> : return_<uint32_t> {};

template<>
struct bytetype<2> : return_<uint16_t> {};

template<>
struct bytetype<1> : return_<uint8_t> {};

最后,你要求的元函数:

template<uint64_t N>
struct Integer : bytetype<bytecount<N>::value> {};

3
这是我见过的最整洁和解释最清晰的模板元编程示例之一 :) - jcoder
2
呵呵...无论如何,我对许多答案的真正“道德问题”是,使用像uint16_t这样的固定宽度类型似乎可以避免任何TMP的需要:如果数据类型的大小已经从标准中知道,并且与平台无关,那么我们实际上不需要C++解决方案- OP的预处理器可以立即做出正确的决策,结果在所有平台上都将相同。我认为如果您想找到适合的原始类型,而不考虑其大小和CHAR_BIT,这将更有趣...但我并不声称那会有用 :-) - Kerrek SB

27
#include <stdint.h>

template<unsigned long long Max>
struct RequiredBits
{
    enum { value =
        Max <= 0xff       ?  8 :
        Max <= 0xffff     ? 16 :
        Max <= 0xffffffff ? 32 :
                            64
    };
};

template<int bits> struct SelectInteger_;
template<> struct SelectInteger_ <8> { typedef uint8_t type; };
template<> struct SelectInteger_<16> { typedef uint16_t type; };
template<> struct SelectInteger_<32> { typedef uint32_t type; };
template<> struct SelectInteger_<64> { typedef uint64_t type; };

template<unsigned long long Max>
struct SelectInteger : SelectInteger_<RequiredBits<Max>::value> {};

int main()
{
    SelectInteger<12345>::type x = 12345;
}

6
注意,256ull * 256 * 256 * 256 * 256 * 256 * 256 * 256 的值为零。 - fredoverflow
2
就我个人而言,我认为这是解决问题的最佳方案。 - fredoverflow
我也喜欢这个 :) 我该如何选择要接受哪一个! - jcoder
@MaximYegorushkin 奥卡姆剃刀原则在软件行话中等同于KISS :) - v.oddou
你确定你的初始化表达式不需要 constexpr 支持吗?换句话说,这真的符合 C++03 标准吗? - v.oddou
显示剩余2条评论

7

您是否一定需要比int类型还小的变量类型中最小的那个呢?

如果不需要,而且您的编译器支持的话,可以这样做:

int main()
{
    typeof('A') i_65 = 0; // declare variable 'i_65' of type 'char'
    typeof(10) i_10 = 0; // int
    typeof(10000) i_10000 = 0; // int
    typeof(1000000000000LL) i_1000000000000 = 0; // int 64
}

1
很好。这个问题被标记为C++,但是任何接受typeof的C编译器也应该接受它。 - Pascal Cuoq

6

那么如何使用条件语句:

#include <type_traits>
#include <limits>

template <unsigned long int N>
struct MinInt
{
  typedef typename std::conditional< N < std::numeric_limits<unsigned char>::max(),
       unsigned char, std::conditional< N < std::numeric_limits<unsigned short int>::max(),
         unsigned short int>::type,
         void*>::type>::type
    type;
};

为了涵盖所有所需类型,必须将其扩展为按顺序的方式;在最后阶段,您可以使用enable_if而不是conditional,如果值过大,则会立即出现实例化错误。


1
不会起作用。std::numeric_limits<unsigned char>::max()是一个函数,将在运行时执行,而std::conditional需要在编译时获取值。 - Nawaz
1
@Nawaz:那不是constexpr吗?如果不是,使用T(-1)代替。 - Kerrek SB
我不认识std::conditional。它是C++0x吗? - jcoder
1
@JohnB:没错,但它也在TR1中,而且你也可以轻松地自己编写它——它只是三元?:的模板版本。 - Kerrek SB
这比其他答案更具可移植性,尽管有点笨重。 - Pharap
显示剩余3条评论

5

C++11让事情变得轻而易举:

#include <cstdint>
#include <limits>
#include <type_traits>


template <class T, class U =
    typename std::conditional<std::is_signed<T>::value,
      std::intmax_t,
      std::uintmax_t
    >::type>
constexpr bool is_in_range (U x) {
  return (x >= std::numeric_limits<T>::min())
      && (x <= std::numeric_limits<T>::max());
}

template <std::intmax_t x>
using int_fit_type =
    typename std::conditional<is_in_range<std::int8_t>(x),
      std::int8_t,
      typename std::conditional<is_in_range<std::int16_t>(x),
        std::int16_t,
        typename std::conditional<is_in_range<std::int32_t>(x),
          std::int32_t,
          typename std::enable_if<is_in_range<std::int64_t>(x), std::int64_t>::type
        >::type
      >::type
    >::type;

template <std::uintmax_t x>
using uint_fit_type =
    typename std::conditional<is_in_range<std::uint8_t>(x),
      std::uint8_t,
      typename std::conditional<is_in_range<std::uint16_t>(x),
        std::uint16_t,
        typename std::conditional<is_in_range<std::uint32_t>(x),
          std::uint32_t,
          typename std::enable_if<is_in_range<std::uint64_t>(x), std::uint64_t>::type
        >::type
      >::type
    >::type;

3

我认为应该选择能容纳给定整数的最小类型:

class true_type {};
class false_type {};

template<bool> 
struct bool2type 
{ 
  typedef true_type  type; 
};

template<>
struct bool2type<false>
{
  typedef false_type  type;
};

template<int M, int L, int H>
struct within_range
{
   static const bool value = L <= M && M <=H;
   typedef typename bool2type<value>::type type;
};

template<int M, class booltype> 
struct IntegerType;

template<int Max> 
struct IntegerType<Max,typename within_range<Max, 0, 127>::type >
{
   typedef char type;
};

template<int Max> 
struct IntegerType<Max,typename within_range<Max, 128, 32767>::type >
{
   typedef short type;
};

template<int Max> 
struct IntegerType<Max,typename within_range<Max, 32768, INT_MAX>::type >
{
   typedef int type;
};

template <int Max>
struct Integer {
    typedef typename IntegerType<Max, true_type>::type type;
};

测试代码:

int main() {
        cout << typeid(Integer<122>::type).name() << endl;
        cout << typeid(Integer<1798>::type).name() << endl;
        cout << typeid(Integer<890908>::type).name() << endl;
        return 0;
}

输出:(c=字符,s=短整型,i=整型-由于名称重整)
c
s
i

示例代码:http://www.ideone.com/diALB

注意:当然,我假设类型的大小范围,即使如此,我可能选择了错误的范围。如果是这样,那么提供正确的范围给 within_range 类模板,就可以为给定的整数选择最小的类型。


2
#include <stdio.h>

#ifdef _MSC_VER
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
#else
#include <stdint.h> // i dunno
#endif

template <class T> struct Printer       { static void print()   { printf("uint64_t\n"); } };
template <> struct Printer<uint32_t>    { static void print()   { printf("uint32_t\n"); } };
template <> struct Printer<uint16_t>    { static void print()   { printf("uint16_t\n"); } };
template <> struct Printer<uint8_t>     { static void print()   { printf("uint8_t\n"); } };

//-----------------------------------------------------------------------------

template <long long N> struct Pick32 { typedef uint64_t type; };
template <> struct Pick32<0> { typedef uint32_t type; };

template <long long N> struct Pick16 { typedef typename Pick32<(N>>16)>::type type; };
template <> struct Pick16<0> { typedef uint16_t type; };

template <long long N> struct Pick8 { typedef typename Pick16<(N>>8)>::type type; };
template <> struct Pick8<0> { typedef uint8_t type; };

template <long long N> struct Integer
{
    typedef typename Pick8<(N>>8)>::type type;
};


int main()
{
    Printer< Integer<0ull>::type >::print(); // uint8_t
    Printer< Integer<255ull>::type >::print(); // uint8_t

    Printer< Integer<256ull>::type >::print(); // uint16_t
    Printer< Integer<65535ull>::type >::print(); // uint16_t

    Printer< Integer<65536ull>::type >::print(); // uint32_t
    Printer< Integer<0xFFFFFFFFull>::type >::print(); // uint32_t

    Printer< Integer<0x100000000ULL>::type >::print(); // uint64_t
    Printer< Integer<1823465835443ULL>::type >::print(); // uint64_t
}

1

下面是无符号类型:

#include <stdint.h>
#include <typeinfo>
#include <iostream>

template <uint64_t N>
struct Integer {
    static const uint64_t S1 = N | (N>>1);
    static const uint64_t S2 = S1 | (S1>>2);
    static const uint64_t S4 = S2 | (S2>>4);
    static const uint64_t S8 = S4 | (S4>>8);
    static const uint64_t S16 = S8 | (S8>>16);
    static const uint64_t S32 = S16 | (S16>>32);
    typedef typename Integer<(S32+1)/4>::type type;
};

template <> struct Integer<0> {
    typedef uint8_t type;
};

template <> struct Integer<1> {
    typedef uint8_t type;
};

template <> struct Integer<256> {
    typedef uint16_t type;
};

template <> struct Integer<65536> {
    typedef uint32_t type;
};

template <> struct Integer<4294967296LL> {
    typedef uint64_t type;
};

int main() {
    std::cout << 8 << " " << typeid(uint8_t).name() << "\n";
    std::cout << 16 << " " << typeid(uint16_t).name() << "\n";
    std::cout << 32 << " " << typeid(uint32_t).name() << "\n";
    std::cout << 64 << " " << typeid(uint64_t).name() << "\n";
    Integer<1000000>::type i = 12;
    std::cout << typeid(i).name() << "\n";
    Integer<10000000000LL>::type j = 12;
    std::cout << typeid(j).name() << "\n";
}

请注意,这并不一定选择最小的适用类型,因为原则上没有阻止实现具有24位整数。但对于“正常”的实现来说,这是可以的,如果要包括不寻常的大小,您需要做的就是更改特化列表。
对于根本没有64位类型的实现,您需要更改模板参数N的类型 - 或者您可以使用uintmax_t。此外,在右移32位的情况下可能会有问题。
对于具有大于uint64_t的类型的实现,也会出现问题。

1
从1000000开始,使用(N+1)/2永远无法达到65536。这种方法行不通。 - Didier Trosset
@Steve:我想听听您对我的解决方案的看法。 :-) - Nawaz
1
@Didier:好观点,我已经修复了。可能还有更优雅的解决方案。 - Steve Jessop

1
#define UINT8_T   256
#define UINT16_T  65536
#define UINT32_T  4294967296

template<uint64_t RANGE, bool = (RANGE < UINT16_T)>
struct UInt16_t { typedef uint16_t type; };
template<uint64_t RANGE>
struct UInt16_t<RANGE, false> { typedef uint32_t type; };

template<uint64_t RANGE, bool = (RANGE < UINT8_T)>
struct UInt8_t { typedef uint8_t type; };
template<uint64_t RANGE>
struct UInt8_t<RANGE, false> { typedef typename UInt16_t<RANGE>::type type; };

template<uint64_t RANGE>
struct Integer {
  typedef typename UInt8_t<RANGE>::type type;
};

您可以扩展到uint64_t或者您的平台支持的任何类型。

演示


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接