Existe-t-il un moyen en C ++ de définir un type suffisamment grand pour contenir au maximum un nombre spécifique, sans doute en utilisant un code de modèle intelligent. Par exemple, je veux pouvoir écrire: –
Integer::type dataItem;
Et ce type se résout-il au plus petit type suffisamment grand pour contenir la valeur spécifiée?
Contexte: Je dois générer des définitions de variables à l’aide d’un script provenant d’un fichier de données externe. Je suppose que je pourrais faire en sorte que le script examine les valeurs et ensuite utiliser uint8_t
, uint16_t
, uint32_t
, etc. en fonction de la valeur, mais il semble plus élégant de construire la taille dans le code C ++ généré.
Je ne vois aucun moyen de créer un modèle capable de faire cela, mais connaître les modèles C ++, je suis sûr qu’il y a un moyen. Des idées?
Boost.Integer a déjà des fonctionnalités pour la sélection de type entier :
boost::int_max_value_t::least
Le plus petit type intégral intégré pouvant contenir toutes les valeurs comsockets entre 0 et V. Le paramètre doit être un nombre positif.
boost::uint_value_t::least
Le plus petit type intégral non signé intégré pouvant contenir toutes les valeurs positives jusqu’à V inclus. Le paramètre doit être un nombre positif.
Bien sûr, c’est possible. Voici les ingrédients. Commençons par mes deux méta-fonctions préférées:
template struct constant { enum { value = N }; }; template struct return_ { typedef T type; };
Ensuite, une méta-fonction qui compte les bits nécessaires pour stocker un nombre:
template struct bitcount : constant<1 + bitcount<(N>>1)>::value> {}; template<> struct bitcount<0> : constant<1> {}; template<> struct bitcount<1> : constant<1> {};
Ensuite, une méta-fonction qui compte les octets:
template struct bytecount : constant<((bitcount::value + 7) >> 3)> {};
Ensuite, une méta-fonction qui renvoie le plus petit type pour un nombre donné d’octets:
template struct bytetype : return_ {}; template<> struct bytetype<4> : return_ {}; template<> struct bytetype<3> : return_ {}; template<> struct bytetype<2> : return_ {}; template<> struct bytetype<1> : return_ {};
Et enfin, la méta-fonction que vous avez demandée:
template struct Integer : bytetype::value> {};
#include template struct RequiredBits { enum { value = Max <= 0xff ? 8 : Max <= 0xffff ? 16 : Max <= 0xffffffff ? 32 : 64 }; }; template 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 struct SelectInteger : SelectInteger_::value> {}; int main() { SelectInteger<12345>::type x = 12345; }
Voulez-vous nécessairement le plus petit, par opposition à utiliser int
pour les types plus petits que int?
Sinon, et votre compilateur le supporte, pourriez-vous faire:
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 }
Que diriez-vous d’un conditionnel:
#include #include template struct MinInt { typedef typename std::conditional< N < std::numeric_limits ::max(), unsigned char, std::conditional< N < std::numeric_limits ::max(), unsigned short int>::type, void*>::type>::type type; };
Cela devrait être étendu à tous les types souhaités, dans l’ordre; au stade final, vous pouvez utiliser enable_if
plutôt que conditional
pour avoir une erreur d’instanciation là-bas si la valeur est trop grande.
Je pense qu’il devrait choisir le plus petit type qui contiendrait l’entier donné:
class true_type {}; class false_type {}; template struct bool2type { typedef true_type type; }; template<> struct bool2type { typedef false_type type; }; template struct within_range { static const bool value = L <= M && M <=H; typedef typename bool2type::type type; }; template struct IntegerType; template struct IntegerType::type > { typedef char type; }; template struct IntegerType::type > { typedef short type; }; template struct IntegerType::type > { typedef int type; }; template struct Integer { typedef typename IntegerType::type type; };
Code de test:
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; }
Sortie: (c = char, s = short, i = int - en raison de la manipulation du nom)
c s i
Démo: http://www.ideone.com/diALB
Note: bien sûr, je suppose la taille et la gamme des types, et même en dépit de cela, je pourrais avoir choisi la mauvaise plage; Si c'est le cas, puis en fournissant la plage correcte au modèle de classe within_range
, on peut choisir le plus petit type pour un entier donné.
Peasy facile avec C ++ 11:
#include #include #include template ::value, std::intmax_t, std::uintmax_t >::type> constexpr bool is_in_range (U x) { return (x >= std::numeric_limits::min()) && (x <= std::numeric_limits ::max()); } template using int_fit_type = typename std::conditional(x), std::int8_t, typename std::conditional(x), std::int16_t, typename std::conditional(x), std::int32_t, typename std::enable_if(x), std::int64_t>::type >::type >::type >::type; template using uint_fit_type = typename std::conditional(x), std::uint8_t, typename std::conditional(x), std::uint16_t, typename std::conditional(x), std::uint32_t, typename std::enable_if(x), std::uint64_t>::type >::type >::type >::type;
#include #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 // i dunno #endif template struct Printer { static void print() { printf("uint64_t\n"); } }; template <> struct Printer { static void print() { printf("uint32_t\n"); } }; template <> struct Printer { static void print() { printf("uint16_t\n"); } }; template <> struct Printer { static void print() { printf("uint8_t\n"); } }; //----------------------------------------------------------------------------- template struct Pick32 { typedef uint64_t type; }; template <> struct Pick32<0> { typedef uint32_t type; }; template struct Pick16 { typedef typename Pick32<(N>>16)>::type type; }; template <> struct Pick16<0> { typedef uint16_t type; }; template struct Pick8 { typedef typename Pick16<(N>>8)>::type type; }; template <> struct Pick8<0> { typedef uint8_t type; }; template 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 }
Vous voulez dire quelque chose comme:
template struct Integer { typedef typename Integer::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; };
Et peut-être une autre structure basée sur des modèles pour UnsignedInteger.
Vous pourriez peut-être même utiliser numeric_limits au lieu des valeurs codées en dur.
Nous y voilà, pour les types non signés:
#include #include #include template 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"; }
Notez que cela ne sélectionne pas nécessairement le plus petit type applicable, car il n'y a rien en principe pour empêcher une implémentation d'avoir un entier de 24 bits. Mais pour les implémentations "normales", c'est correct, et pour y remédier, il suffit de changer la liste des spécialisations.
Pour les implémentations qui ne possèdent pas de type 64 bits, vous devez modifier le type du paramètre N
- ou utiliser uintmax_t
. Dans le cas également, le changement de vitesse de 32 pourrait être difficile.
Pour les implémentations qui ont un type plus grand que uint64_t
, il y a aussi des problèmes.
#define UINT8_T 256 #define UINT16_T 65536 #define UINT32_T 4294967296 template struct UInt16_t { typedef uint16_t type; }; template struct UInt16_t { typedef uint32_t type; }; template struct UInt8_t { typedef uint8_t type; }; template struct UInt8_t { typedef typename UInt16_t::type type; }; template struct Integer { typedef typename UInt8_t::type type; };
Vous pouvez étendre jusqu’à uint64_t
ou tout ce que votre plate-forme prend en charge.
Démo .
Pas de enum, juste du typedef.
#include #include template 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