c++ templates template-meta-programming

c++ - Elija automáticamente un tipo de variable lo suficientemente grande como para contener un número especificado



templates template-meta-programming (12)

¿Desea necesariamente el más pequeño, en lugar de usar int para tipos más pequeños que int?

Si no es así, y su compilador lo admite, podría hacerlo:

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 }

¿Hay alguna manera en C ++ de definir un tipo que sea lo suficientemente grande como para contener como máximo un número específico, presumiblemente utilizando algún código de plantilla inteligente? Por ejemplo, quiero poder escribir:

Integer<10000>::type dataItem;

¿Y ese tipo de resolver al tipo más pequeño que es lo suficientemente grande como para mantener el valor especificado?

Antecedentes: necesito generar algunas definiciones de variable usando una secuencia de comandos desde un archivo de datos externo. Supongo que podría hacer que el script vea los valores y luego use uint8_t , uint16_t , uint32_t , etc. según el valor, pero parece más elegante construir el tamaño en el código C ++ generado.

No veo ninguna manera de hacer una plantilla que pueda hacer esto, pero conociendo las plantillas C ++, estoy seguro de que hay una manera. ¿Algunas ideas?


¿Qué tal un condicional:

#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; };

Esto debería extenderse para abarcar todos los tipos deseados, en orden; en la etapa final, puede usar enable_if lugar de conditional para tener un error de instanciación allí mismo si el valor es demasiado grande.


Aquí vamos, para tipos sin firmar:

#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"; }

Tenga en cuenta que esto no necesariamente elige el tipo más pequeño aplicable, ya que no hay nada en principio para evitar que una implementación tenga un entero de 24 bits. Pero para las implementaciones "normales" está bien, y para incluir tamaños inusuales todo lo que necesita hacer para solucionarlo es cambiar la lista de especializaciones.

Para las implementaciones que no tienen un tipo de 64 bits, debe cambiar el tipo del parámetro de plantilla N , o puede usar uintmax_t . También en el caso el cambio a la derecha por 32 podría ser dudoso.

Para las implementaciones que tienen un tipo más grande que uint64_t , también hay problemas.


Boost.Integer ya tiene instalaciones para la selección de tipos de enteros :

boost::int_max_value_t<V>::least

El tipo integral más pequeño, incorporado, firmado que puede contener todos los valores en el rango inclusivo 0 - V. El parámetro debe ser un número positivo.

boost::uint_value_t<V>::least

El tipo integral más pequeño, incorporado, sin signo que puede contener todos los valores positivos hasta e incluyendo V. El parámetro debe ser un número positivo.


Claro, es posible. Aquí están los ingredientes. Comencemos con mis dos metafunciones favoritas:

template<uint64_t N> struct constant { enum { value = N }; }; template<typename T> struct return_ { typedef T type; };

Luego, una metafunción que cuenta los bits necesarios para almacenar un número:

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

Entonces, una metafunción que cuenta los bytes:

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

Luego, una metafunción que devuelve el tipo más pequeño para un número dado de bytes:

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> {};

Y finalmente, la metafunción que solicitó:

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


Creo que debería elegir el tipo más pequeño que contenga el entero dado:

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; };

Código de prueba:

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; }

Salida: (c = char, s = corto, i = int - debido al cambio de nombre)

c s i

Demostración: http://www.ideone.com/diALB

Nota: por supuesto, estoy asumiendo el tamaño y el rango de los tipos, e incluso a pesar de esto, podría haber elegido el rango incorrecto; si es así, proporcionando el rango correcto a la plantilla de clase within_range , se puede elegir el tipo más pequeño para un entero dado.


Easy peasy con 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;


No enum, solo 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


Te refieres a algo como:

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; };

Y tal vez otra estructura con plantillas para UnsignedInteger.

Incluso podría usar numeric_limits en lugar de los valores codificados.


#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; };

Puede extender hasta uint64_t o lo que sea que admita su plataforma.

Demo


#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; }


#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 }