Автоматически выбирать тип переменной, достаточно большой, чтобы содержать указанное число - PullRequest
54 голосов
/ 12 августа 2011

Есть ли способ в C ++ определить тип, который достаточно большой, чтобы содержать не более определенного числа, предположительно, с использованием некоторого умного кода шаблона.Например, я хочу иметь возможность написать: -

Integer<10000>::type dataItem;

И разрешить ли этот тип наименьшему типу, который достаточно большой, чтобы содержать указанное значение?

Справочная информация: мне нужногенерировать определения переменных с использованием сценария из внешнего файла данных.Я думаю, я мог бы заставить скрипт посмотреть значения, а затем использовать uint8_t, uint16_t, uint32_t и т. Д. В зависимости от значения, но кажется, что элегантный выглядит более сложным, чтобы встроить размерсгенерированный код C ++.

Я не вижу способа создать шаблон, который бы мог это сделать, но, зная шаблоны C ++, я уверен, что есть способ.Есть идеи?

Ответы [ 12 ]

57 голосов
/ 12 августа 2011

Boost.Integer уже имеет средства для Выбор целочисленных типов :

boost::int_max_value_t<V>::least

Наименьший встроенный целочисленный тип со знаком, который может содержать все значения ввключающий диапазон 0 - V. Параметр должен быть положительным числом.

boost::uint_value_t<V>::least

Наименьший встроенный беззнаковый целочисленный тип, который может содержать все положительные значения вплоть до V. Параметр должен быть положительным числом.

41 голосов
/ 12 августа 2011

Конечно, это возможно. Вот ингредиенты. Давайте начнем с двух моих любимых мета-функций:

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> {};
25 голосов
/ 12 августа 2011
#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;
}
7 голосов
/ 12 августа 2011

Обязательно ли вам наименьшее, в отличие от использования 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
}
6 голосов
/ 12 августа 2011

Как насчет условного:

#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, чтобы получить ошибку экземпляра прямо здесь, если значение слишком велико.

4 голосов
/ 07 июля 2015

Легко peasy с 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 голосов
/ 12 августа 2011

Я думаю, что следует выбрать наименьший тип, который будет содержать данное целое число:

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,я = int - из-за искажения имени)

c
s
i

Демонстрация: http://www.ideone.com/diALB

Примечание: конечно, я принимаю размер и диапазон типов, и даже несмотря на это, я мог бы выбрать неправильный диапазон;если это так, то, предоставляя правильный диапазон для шаблона класса within_range, можно выбрать наименьший тип для данного целого числа.

2 голосов
/ 12 августа 2011
#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 голос
/ 12 августа 2011
#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 или любой другой поддерживаемой вашей платформы.

Демо .

1 голос
/ 12 августа 2011

Здесь, для типов без знака:

#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, тоже есть проблемы.

...