⬆️ ⬇️

C ++ cross-platform integral types

In my library I try to write cross-platform code, where possible, according to the C ++ standard, so for integral types I use only the standard “ten” (signed char, unsigned char, short, unsigned short, int, unsigned int, long, unsigned long, long long, unsigned long long) and no DWORD, __int64, etc. However, sometimes you want to determine the type of 4 bytes to exactly 4, and not the "system word", "address size", etc. The C ++ standard only speaks in vain: char can no longer be short, which cannot be greater than int, which cannot be longer than long, which cannot be greater than long long.



Having embraced the magic of generic programming using patterns, I decided to implement a cross-platform class that generates an integral type of the required size at compile time. The basic techniques that I use for this are recurrent type redefinition and partial specialization of templates.



Let's start with the formalization of the definition. For each standard type, it is necessary to determine the “next” type, which is necessary for the search algorithm of suitable variants. Using partial specialization, we implement the type_traits template:



template<typename type> struct type_traits; template<> struct type_traits <unsigned char> { typedef unsigned char current_type; typedef unsigned short next_type; }; template<> struct type_traits <unsigned short> { typedef unsigned short current_type; typedef unsigned int next_type; }; template<> struct type_traits <unsigned int> { typedef unsigned int current_type; typedef unsigned long next_type; }; template<> struct type_traits <unsigned long> { typedef unsigned long current_type; typedef unsigned long long next_type; }; template<> struct type_traits <unsigned long long int> { typedef unsigned long long int current_type; }; template<> struct type_traits <signed char> { typedef signed char current_type; typedef short next_type; }; template<> struct type_traits <short> { typedef short current_type; typedef int next_type; }; template<> struct type_traits <int> { typedef int current_type; typedef long next_type; }; template<> struct type_traits <long> { typedef long current_type; typedef long long next_type; }; template<> struct type_traits <long long int> { typedef long long int current_type;}; 


The peculiarity of the types (unsigned) long long int is that next_type is not defined for them, since there is nothing more guaranteed for them.

')

Next, you should determine the basic pattern of the selection algorithm, which contains two parameters: type is a standard numeric type and bool variable, which is true if this type is suitable in size or false if it is not. In the default implementation, we take the type “current type” from the type - type_traits :: current_type, and if the type does not match, we take the “next type” - type_traits :: next_type:



 //    template<typename type, bool> struct type_choice { typedef typename type_traits<type>::current_type std_type; }; template<typename type> struct type_choice<type, false> { typedef typename type_traits<type>::next_type next_type; typedef typename type_choice<next_type, sizeof(next_type) == capacity>::std_type std_type; }; 


The third service template is designed to select the initial version, which we have two, depending on whether we want to use the signed or unsigned type - char or unsigned char:



  //      template <bool is_signed> struct base_type_selector { typedef signed char base_type; }; template <> struct base_type_selector<false> { typedef unsigned char base_type; }; 


Finally, you need to define the main class, which will contain the desired type. I called this class fixed_int, it has two template parameters: the first parameter is of type size_t and indicates the desired capacity in bytes, the second parameter is boolean and is responsible for the type sign. The class of open entities contains only one magic typedef:



 typedef typename type_choice< typename base_type_selector<is_signed>::base_type, sizeof(base_type_selector<is_signed>::base_type) == capacity >::std_type type; 


You can arrange the main and service classes in different ways. Wise MVS compiler compile local template classes without hesitation:



 template <size_t capacity, bool is_signed> class fixed_int { //          template <int x> struct unsupported_capacity { int i[1/(xx)]; }; template <> struct unsupported_capacity<1> {}; template <> struct unsupported_capacity<2> {}; template <> struct unsupported_capacity<4> {}; template <> struct unsupported_capacity<8> {}; //   ,    template<typename type> struct type_traits; template<> struct type_traits <unsigned char> { typedef unsigned char current_type; typedef unsigned short next_type; }; template<> struct type_traits <unsigned short> { typedef unsigned short current_type; typedef unsigned int next_type; }; template<> struct type_traits <unsigned int> { typedef unsigned int current_type; typedef unsigned long next_type; }; template<> struct type_traits <unsigned long> { typedef unsigned long current_type; typedef unsigned long long next_type; }; template<> struct type_traits <unsigned long long int> { typedef unsigned long long int current_type; typedef unsupported_capacity<capacity> next_type; }; template<> struct type_traits <signed char> { typedef signed char current_type; typedef short next_type; }; template<> struct type_traits <short> { typedef short current_type; typedef int next_type; }; template<> struct type_traits <int> { typedef int current_type; typedef long next_type; }; template<> struct type_traits <long> { typedef long current_type; typedef long long next_type; }; template<> struct type_traits <long long int> { typedef long long int current_type; typedef unsupported_capacity<capacity> next_type;}; //    template<typename type, bool> struct type_choice { typedef typename type_traits<type>::current_type std_type; }; template<typename type> struct type_choice<type, false> { typedef typename type_traits<type>::next_type next_type; typedef typename type_choice<next_type, sizeof(next_type) == capacity>::std_type std_type; }; //      template <bool is_signed> struct base_type_selector { typedef signed char base_type; }; template <> struct base_type_selector<false> { typedef unsigned char base_type; }; public: typedef typename type_choice< typename base_type_selector<is_signed>::base_type, sizeof(base_type_selector<is_signed>::base_type) == capacity >::std_type type; }; 


Less intelligent compilers may not understand this construction, for example, Qt complains about partial specialization of a template class inside another template class. For such cases, internal service templates can be rendered separately in the namespace of __private, in order not to litter the common namespace, this method in such cases uses Alexandrescu in its Loki library (for example, for type lists).



It remains to add convenient names for all types, for example:



 typedef fixed_int<1, false>::type uint8; typedef fixed_int<2, false>::type uint16; typedef fixed_int<4, false>::type uint32; typedef fixed_int<8, false>::type uint64; typedef fixed_int<1, true>::type int8; typedef fixed_int<2, true>::type int16; typedef fixed_int<4, true>::type int32; typedef fixed_int<8, true>::type int64; 


... and check what came out of all this (launched under MVS2015 / intel 0x86):



 ... int32 x1; uint64 x2; fixed_int<2, true>::type x3; std::wcout<<typeid(x1).name()<<std::endl; std::wcout<<typeid(x2).name()<<std::endl; std::wcout<<typeid(x3).name()<<std::endl; ... 


Result:



 int unsigned __int64 short 


So we got cross-platform fixed types that do not use any third-party information for their definition. As a fee, additional calculations are made at the compilation stage. Incorrect template parameters or the inability to maintain this dimension on any platform will lead to a compilation error, which is also a plus.



PS: Since the description of template instantiation errors suffers from some complexity, I used a non-controversial technique: defining a template helper class that compiles only partial specializations:



  //          template <int x> struct unsupported_capacity { int i[1/(xx)]; }; template <> struct unsupported_capacity<1> {}; template <> struct unsupported_capacity<2> {}; template <> struct unsupported_capacity<4> {}; template <> struct unsupported_capacity<8> {}; 


Not indisputable mainly because the description of errors in the standard is not defined, and then the benefits of this class are not guaranteed. Microsoft's compiler when trying to instantiate this type of fixed_int <3, true> :: type gives an error:



 exp4.cpp(127): error C2057: expected constant expression exp4.cpp(156) : see reference to class template instantiation 'fixed_int<3,true>::unsupported_capacity<3>' being compiled ... 




PS: Replaced char types with signed char and (unsigned) long long with (unsigned) long long int

Source: https://habr.com/ru/post/280542/



All Articles