58

在 C++ 中有什么方法可以定义一个足够大的类型,最多可以容纳一个特定的数字,大概是使用一些聪明的模板代码。例如,我希望能够写:-

Integer<10000>::type dataItem;

并且该类型是否解析为足以容纳指定值的最小类型?

背景:我需要使用来自外部数据文件的脚本生成一些变量定义。我想我可以让脚本查看值,然后根据值使用uint8_t, uint16_t,等,但是将大小构建到生成的 C++ 代码中uint32_t似乎更优雅。

我看不到任何方法可以制作可以做到这一点的模板,但是知道 C++ 模板,我确信有办法。有任何想法吗?

4

15 回答 15

62

Boost.Integer 已经有Integer Type Selection的功能:

boost::int_max_value_t<V>::least

最小的内置有符号整数类型,可以容纳 0 - V 范围内的所有值。参数应为正数。

boost::uint_value_t<V>::least

最小的、内置的、无符号整数类型,可以保存所有正值,包括 V。参数应该是一个正数。

于 2011-08-12T12:07:27.737 回答
47

当然,这是可能的。这里是成分。让我们从我最喜欢的两个元函数开始:

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> {};
于 2011-08-12T11:24:48.257 回答
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;
}
于 2011-08-12T11:12:28.490 回答
7

您是否一定想要最小的,而不是使用int小于 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
}
于 2011-08-12T14:20:30.107 回答
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

于 2011-08-12T10:43:17.423 回答
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;
于 2015-07-07T15:02:59.603 回答
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=char, s=short, i=int - 由于名称修改)

c
s
i

演示:http ://www.ideone.com/diALB

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

于 2011-08-12T11:06:24.757 回答
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
}
于 2011-08-12T11:02:04.497 回答
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有麻烦。

于 2011-08-12T10:48:10.813 回答
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或您的平台支持的任何内容。

演示

于 2011-08-12T11:36:45.977 回答
1

没有枚举,只有 typedef。

#include<stdio.h>
#include<stdint.h>

template <unsigned long long V> struct valuetype
{
    typedef typename valuetype<(V & (V-1)) ? (V & (V-1)) : (V >> 1)>::val val;
};
template <> struct valuetype<(1ull << 0)> { typedef uint8_t val; };
template <> struct valuetype<(1ull << 8)> { typedef uint16_t val; };
template <> struct valuetype<(1ull << 16)> { typedef uint32_t val; };
template <> struct valuetype<(1ull << 32)> { typedef uint64_t val; };

int main ()
{
    valuetype<123>::val a = ~0;
    printf ("%llu\n", (unsigned long long) a);  
    valuetype<456>::val b = ~0;
    printf ("%llu\n", (unsigned long long) b);  
    valuetype<123456>::val c = ~0;
    printf ("%llu\n", (unsigned long long) c);  
    valuetype<123456123>::val d = ~0;
    printf ("%llu\n", (unsigned long long) d);
    valuetype<123456123456>::val e = ~0;
    printf ("%llu\n", (unsigned long long) e);
    return 0;
}

255
65535
4294967295
4294967295
18446744073709551615

于 2017-01-24T20:08:09.757 回答
1

一个(恕我直言)更具可读性的 C++11 版本:

#include <inttypes.h>
#include <cstdlib>
#include <type_traits>
#include <typeinfo>
#include <iostream>


template <long long n, typename ...An>
struct inttype;

template <long long n, typename A0, typename ...An>
struct inttype<n, A0, An...>{
  typedef A0 least;
};

template <long long n, typename A0, typename A1, typename ...An>
struct inttype<n, A0, A1, An...>{
  typedef typename std::conditional< n == (A0) n, A0, typename inttype<n, A1, An...>::least >::type least ;
};

template <long long n>
struct inttype<n>{
  typedef typename inttype<n, uint8_t, uint16_t, uint32_t, uint64_t>::least least;
};


int main(int argc, char * argv[])
{
  std::cout << sizeof(inttype<0x0ULL>::least) << std::endl;
  std::cout << sizeof(inttype<0xFFULL>::least) << std::endl;
  std::cout << sizeof(inttype<0xFFFULL>::least) << std::endl;
  std::cout << sizeof(inttype<0xFFFFFULL>::least) << std::endl;
  std::cout << sizeof(inttype<0xFFFFFFFFFULL>::least) << std::endl;
}

传递给 inttype 你想持有的数字,以及你想尝试的类型,以升序 sizeof 的形式传递。然后它会选择第一种类型,这样铸造n就不会改变n。如果没有指定类型(Op 的情况)默认为所有 uint 类型

于 2020-07-09T11:31:08.580 回答
0

你的意思是这样的:

template <int MAX>
struct Integer {
    typedef typename Integer<MAX+1>::type type;
};

template <>
struct Integer<2147483647> {
    typedef int32_t type;
};

template <>
struct Integer<32767> {
    typedef int16_t type;
};

template <>
struct Integer<127> {
    typedef int8_t type;
};

也许还有另一个 UnsignedInteger 的模板化结构。

您甚至可以使用 numeric_limits 而不是硬编码值。

于 2011-08-12T10:36:00.827 回答
0

我有点晚了,但是...

#include <cstdint>
#include <cstdio>
#include <tuple>

template<uint64_t data, int8_t test_bit= sizeof(data)-1>
struct getMinimalByteSize{
    using type= typename std::conditional< (bool)(data & (uint64_t)0xFFL << (test_bit*8)),
        typename std::tuple_element_t<test_bit, std::tuple<uint8_t, uint16_t, uint32_t, uint32_t, uint64_t, uint64_t, uint64_t, uint64_t>>,
        typename getMinimalByteSize<data, test_bit - 1>::type>::type;};

template<uint64_t data>
struct getMinimalByteSize<data, -1>{using type = uint64_t;};

int main()
{
  static_assert(sizeof(getMinimalByteSize<0x0>::type)==8);
  static_assert(sizeof(getMinimalByteSize<0xFF>::type)==1);
  static_assert(sizeof(getMinimalByteSize<0xFFF>::type)==2);
  static_assert(sizeof(getMinimalByteSize<0xFFFFF>::type)==4);
  static_assert(sizeof(getMinimalByteSize<0xFFFFFFFFF>::type)==8);
}

与所有其他方法的区别在于测试。它不是测试该值是否大于给定 N 位可能的最大数字,而是逐个字节地测试它是否是最后一个(最重要的)非零字节。如果是,那么这是所需的最小位数。最后,我们使用手工制作的列表来修复 C++ 中没有定义 24、48、56 位整数的事实。

这就是这个模板元程序看起来像一个简单的 C 函数的样子:

#include <stddef.h>

int tuple_element_t[]={8,16,32,32,64,64,64,64,64};

int getMinimalByteSize(uint64_t data, int8_t first_hi_byte = sizeof(data)-1){
    if (!data) return 0;
    /* Does the N bit of test is set? If so, we are done*/
    if (data &  (uint64_t)0xFFL << (first_hi_byte*8))
        return tuple_element_t[first_hi_byte];
    else/*Else, we tray with the next bit*/
        return getMinimalByteSize(data, first_hi_byte-1);}

如果您没有第一次看到它,请不要担心,给自己时间。我在 AVR 上工作了 10 多年,在一个每个字节都很重要的平台上。如果你在不到 10 年的时间里理解它,你就已经打败了我。

于 2020-06-26T00:01:19.677 回答
0

对于enums,了解 可能很有用std::underlying_type

例子:

typedef enum {west, north, east, south, dir_count} dir_t;
std::underlying_type_t<dir_t> tmp;
于 2021-04-21T14:55:26.663 回答