Автоматически выберите тип переменной, достаточно большой для хранения указанного числа
есть ли способ в C++ определить тип, который достаточно велик, чтобы содержать не более определенного числа, предположительно используя какой-то умный код шаблона. Например, я хочу иметь возможность писать : -
Integer<10000>::type dataItem;
и разрешите этот тип до самого маленького типа, который достаточно велик, чтобы удерживать указанное значение?
фон: мне нужно создать некоторые определения переменных с помощью скрипта из внешнего файла данных. Я думаю, я мог бы сделать скрипт посмотреть на значения, а затем использовать uint8_t
,uint16_t
,uint32_t
и т. д. в зависимости от значения, но кажется больше элегантных чтобы построить размер в сгенерированном коде C++.
Я не вижу никакого способа сделать шаблон, который может это сделать, но зная шаблоны C++, я уверен, что есть способ. Есть идеи?
12 ответов:
импульс.Integer уже имеет средства для Выбор Целочисленного Типа:
boost::int_max_value_t<V>::least
самые маленькие, встроенные, знаковый тип, который может содержать все значения в диапазоне 0 - В. параметр должен быть положительным числом.
boost::uint_value_t<V>::least
наименьший, встроенный, беззнаковый интегральный тип, который может содержать все положительные значения вплоть до V. параметр должен быть положительным числом.
конечно, это возможно. Вот ингредиенты. Давайте начнем с моих двух любимых мета-функций:
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> {};
#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; }
вы обязательно хотите самый маленький, в отличие от использования
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 }
как насчет условного:
#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
чтобы иметь ошибку создания экземпляра прямо там, если значение слишком велико.
Я думаю, что он должен выбрать наименьший тип, который будет содержать данное целое число:
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
шаблон класса, можно выбрать наименьший тип для данного целого числа.
легкий 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;
#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 }
Вы имеете в виду что-то вдоль линий:
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 вместо жестко закодированных значений.
здесь мы идем, для беззнаковых типов:
#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
, там тоже беда.
#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
или все, что поддерживает ваша платформа.демо.
нет перечисления, просто 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