Introduction

I use enumerators many times in C++ but sadly these enums are not supported well. When you want to serialize an enum, you don't know what is the smallest integer type which can safely store the values of this enum. Of course you can choose the biggest integer but it is not efficient and in this case, you don't handle the signed/unsigned integers.
I wrote a small class which can determine the right size of the enum in compile time. To do this there are some restrictions:

  • The smallest value in the enum must be the _MIN_VALUE
  • The highest value in the enum must be the _MAX_VALUE
  • The enum must be nested into a struct, class or namespace (see the example) 
If you meet these requirements, the smallest size can be determined correctly. Otherwise you will get compile error.

In this article I used the Loki library. For more information, visit the site

Using the code

It is very easy to use the class. Let's see!

struct ResultLevel
{
	enum Value
	{
		_MIN_VALUE = -4,
		
		FATAL_ERROR,
		CRITICAL_ERROR,
		ERROR,
		OK,
		WARNING,
		
		_MAX_VALUE
	};
};

// ...

Integral<ResultLevel>::Type level = 0; // the Type will be signed char (int8)
					// _MIN_VALUE = -4 and _MAX_VALUE = 2

// ...

struct ASCII
{
	enum Value
	{
		_MIN_VALUE,
		
		MIN_CONTROL = _MIN_VALUE,
		MAX_CONTROL = 31,
		
		MIN_PRINTABLE = MAX_CONTROL + 1,
		MAX_PRINTABLE = 127,
		
		MIN_EXTENDED = MAX_PRINTABLE + 1,
		MAX_EXTENDED = 255,
		
		_MAX_VALUE = MAX_EXTENDED
	};
};

// ...

Integral<ASCII>::Type ascii = 0; // the Type will be unsigned char (uint8)
				// _MIN_VALUE = 0 and _MAX_VALUE = 255
The Type of the Integral will be the expected type. As you see, it handles the signed and unsigned integers, too and of course you can use every signed and unsigned integers (char, short, int, long, long long).

The implementation

Integral

The main class which is called every time is the Integral class.

template <typename _TEnumerator, typename _TTypes = void> struct Integral;

template <typename _TEnumerator, typename _TTypes>
struct Integral
{
protected:
	
	typedef typename _TTypes::Result Types;
	typedef typename Loki::TL::TypeAt<Types, 0>::Result TFirstType;
	
public:
	
	typedef typename FindIntegral<_TEnumerator, TFirstType, Types, false>::Type Type;
};

template <typename _TEnumerator>
struct Integral<void>
{
protected:
	
	typedef Loki::TL::MakeTypelist<uint8, int8, uint16, int16, uint32, int32, uint64, int64, uintmax, intmax>::Result Types;
	
public:
	
	typedef typename Integral<_TEnumerator, Types>::Type Type;
	
};
Integral<>::Types contains the integers in ascendent order which will be checked when we are looking for the right integer type. It is important for the list to be in ascendent order because the search is linear and will stop on the first match.
The intmax and uintmax are typedefs to intmax_t and uintmax_t which are part of the C99 ANSI standard. These types are the largest storable integers by the processor on the current system. For more information see the section 7.18.1.5 in the standard or on Wiki.
Integral<>::TFirstType is just a helper typedef. This is the current type checked by FindIntegral.
Integral<>::Type is a typedef and it uses the FindIntegral template which does the whole job. The other two typedefs above are the helpers for this.

FindIntegral

The FindIntegral consists of two parts. One of them implements the search and the other one (specialized template) is used when the type has been found.
It requires 4 template parameters:

  • _TEnumerator is the enumerator type.
  • _TFirstType integer type will be checked in the class. If it meets the expectations, the specialized template will be instantiated.
  • _TTypes is a Loki TypeList filled with integers.
  • isFound controls the instatiation of the template. If it is true, we have found the right integer and the specialized template will be instantiated and the search will be finished.

template <typename _TEnumerator, typename _TFirstType, typename _TTypes, bool isFound> struct FindIntegral; // declaration

template <typename _TEnumerator, typename _TFirstType, typename _TTypes>
struct FindIntegral<_TEnumerator, _TFirstType, _TTypes, true> // we have found the type
{
	typedef _TFirstType Type;
};

template <typename _TEnumerator, typename _TFirstType, typename _TTypes, bool isFound>
struct FindIntegral
{
	typedef _TFirstType TFirstType;
	typedef typename Loki::TL::TypeAt<_TTypes, 0>::Result TNextType;

	static const _TFirstType cMinValue = Base::Type<_TFirstType>::cMinValue;
	static const _TFirstType cMaxValue = Base::Type<_TFirstType>::cMaxValue;
	static const bool isSigned = _TEnumerator::_MIN_VALUE < 0;
	template <bool /* isSigned */, typename _TNull = void> struct Check;
	template <typename _TNull>
	struct Check<true, _TNull>
	{
		
		static const bool isInRange = (_TEnumerator::_MIN_VALUE >= static_cast<intmax>(cMinValue) &&
								   _TEnumerator::_MAX_VALUE <= static_cast<intmax>(cMaxValue));
	};
	template <typename _TNull>
	struct Check<false, _TNull>
	{
		static const bool isInRange = (_TEnumerator::_MIN_VALUE >= static_cast<uintmax>(cMinValue) &&
								   _TEnumerator::_MAX_VALUE <= static_cast<uintmax>(cMaxValue));
	};
	
	typedef typename FindIntegral<_TEnumerator, TNextType, 
		typename Loki::TL::Erase<_TTypes, TFirstType>::Result,
		Check<isSigned>::isInRange >::Type Type;
};

There is a nested template class Check<> which checks that the enum value can be stored safely in the current integer type _TFirstType. If isInRange is true, then the right interger type has been found and the specialized FindIntegral template will be instantiated and the search will be stopped.
I used some static const variables as a helper to check the range; cMinValue is the lowest value and the cMaxValue is the lagest value of the _TFirstType. I had to implement a Type template to store the lowest and largest values for the integer types. It was necessary because I have to check these values in compile time for any integer types. The template is very simple:
template <typename _TType> struct Type;

template <>
struct Type<uint8>
{
	static const uint8 cMinValue = 0;
    static const uint8 cMaxValue = UINT8_MAX;
    static const bool cIsSigned = false;
};

// and so on ...

As you see, you must declare explicitly for every integer type; if you miss it, you'll get compile error.
Let's go back to FindIntegral; the isSigned used for Check and determines which specialized template will be instantiated. The _TEnumerator::_MIN_VALUE tells that the enumerator is signed or unsigned.
The sign checking is necessary because we have to cast the cMinValue and cMaxValue to the largest integer type to avoid compile warnings or any other problem and it's now safe, because we know the sign of the _TEnumerator::_MIN_VALUE.
The value of the Check::isInRange determines which FindIntegral template will be instantiated. If the type has been found, the "iteration" stops and the Type typedef contains the right type.
One more restriction: the order of the integer type must be incremental, started from the smallest type to the largest.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架