/// @file EnumInternal.h /// Internal definitions for the enum type generator in `Enum.h`. /// /// Several definitions must precede the public `ENUM` macro and the interface /// defined in it. This includes helper classes and all `constexpr` functions, /// which cannot be forward-declared. In order to make `Enum.h` more readable, /// these definitions are placed into this file, which is included from /// `Enum.h`. /// /// Throughout the internal code, macro and template parameters named `EnumType` /// stand for the class types generated by the `ENUM` macro, while parameters /// named `EnumValue` stand for the internal C++ enum types. Roughly, /// `EnumValue == EnumType::_Value`. /// /// @todo Generating the values array using the `_eat_assign` template is /// expensive, and the cost seems to be due to the instantiation of /// compile-time objects, not due to templates. Trying statement expressions /// (a GNU extension) didn't work, because statement expressions aren't /// allowed "at file scope" (in this case, within a class type declared at /// file scope). /// @todo Compile time is currently dominated by the cost of static /// instantiation. Try to reduce this cost by statically instantiating data /// structures for each type, then dynamically passing them to a small /// number of actual processing functions - which only have to be /// instantiated once for every different underlying type. Underlying types /// are very likely to collide. #pragma once #include // For size_t. #include // For string and memory routines. #include #include #include "enum_preprocessor_map.h" /// Internal namespace for compile-time and private run-time functions used by /// the enum class generator. namespace _enum { /// Weak symbols to allow the same data structures to be defined statically in /// multiple translation units, then be collapsed to one definition by the /// linker. #define _ENUM_WEAK __attribute__((weak)) /// Template for iterable objects over enum names and values. /// /// The iterables are intended for use with C++11 `for-each` syntax. They are /// returned by each enum type's static `names()` and `values()` methods. For /// example, `EnumType::values()` is an iterable over valid values of type /// `EnumType`, and allows the following form: /// /// ~~~{.cc} /// for (EnumType e : EnumType::values()) { /// // ... /// } /// ~~~ /// /// The iterable class is templated to reuse code between the name and value /// iterables. /// /// @tparam Element Type of element returned during iteration: either the enum /// type (for iterables over `values()`) or `const char*` (for iterables /// over `names()`). /// @tparam EnumType The enum type. /// @tparam ArrayType Type of the array actually being iterated over. The reason /// this is a type parameter is because for the iterable over `values()`, /// the underlying array type is `const EnumType::_value * const`, instead /// of `const EnumType * const`, as one might first expect. Objects of type /// `EnumType` are constructed on the fly during iteration from values of /// type `EnumType::_value` (this is a no-op at run-time). For iterables /// over `names()`, `ArrayType` is simply `const char * const`, as would be /// expeted. /// /// @internal /// /// An `_Iterable` stores a reference to the array (of either names or values) /// that will be iterated over. `_Iterable::iterator` additionally stores an /// index into the array. The iterator begins at the first valid index. Each /// time it is incremented, the iterator advances to the next valid index. The /// `end()` iterator stores an index equal to the size of the array. Values are /// considered valid if they are not equal to the bad value, are not below the /// minimum value, and are not above the maximum value. Names are valid if they /// are the name of a valid value. template class _Iterable; template class _BaseIterator { public: Derived& operator ++() { ++_index; return static_cast(*this); } constexpr bool operator ==(const Derived &other) const { return other._index == _index; } constexpr bool operator !=(const Derived &other) const { return other._index != _index; } protected: constexpr _BaseIterator(size_t index) : _index(index) { } size_t _index; }; template class _ValueIterator : public _BaseIterator<_ValueIterator> { using _Super = _BaseIterator<_ValueIterator>; public: constexpr typename EnumType::Enumerated operator *() const { return EnumType::_value_array[_Super::_index]; } private: using _Super::_Super; friend _Iterable>; }; template class _NameIterator : public _BaseIterator<_NameIterator> { using _Super = _BaseIterator<_NameIterator>; public: const char* operator *() const { return EnumType::_getProcessedName(_Super::_index); } private: using _Super::_Super; friend _Iterable>; }; template class _Iterable { public: using iterator = Iterator; constexpr iterator begin() const { return iterator(0); } constexpr iterator end() const { return iterator(EnumType::size); } constexpr size_t size() const { return EnumType::size; } private: constexpr _Iterable() { }; friend EnumType; }; /// Compile-time helper class used to transform expressions of the forms `A` and /// `A = 42` into values of type `UnderlyingType` that can be used in /// initializer lists. The `ENUM` macro is passed a mixture of simple enum /// constants (`A`) and constants with an explicitly-assigned value (`A = 42`). /// Both must be turned into expressions of type `UnderlyingType` in order to be /// usable in initializer lists of the values array. This is done by prepending /// a cast to the expression, as follows: /// ~~~{.cc} /// (_eat_assign)A /// (_eat_assign)A = 42 /// ~~~ /// The second case is the interesting one. At compile time, the value `A` is /// first converted to an equivalent `_eat_assign` object, that /// stores the value. This object has an overriden assignment operator, which /// "eats" the `= 42` and returns the stored value of `A`, which is then used in /// the initializer list. /// @tparam UnderlyingType Final type used in the values array. template class _eat_assign { private: UnderlyingType _value; public: explicit constexpr _eat_assign(UnderlyingType value) : _value(value) { } template constexpr UnderlyingType operator =(Any dummy) const { return _value; } constexpr operator UnderlyingType () const { return _value; } }; /// Prepends its second argument with the cast `(_eat_assign)` /// in order to make it usable in initializer lists. See `_eat_assign`. #define _ENUM_EAT_ASSIGN_SINGLE(UnderlyingType, expression) \ ((_enum::_eat_assign)UnderlyingType::expression) /// Prepends each of its arguments with the casts /// `(_eat_assign)`, creating the elements of an initializer /// list of objects of type `UnderlyingType`. #define _ENUM_EAT_ASSIGN(UnderlyingType, ...) \ _ENUM_PP_MAP(_ENUM_EAT_ASSIGN_SINGLE, UnderlyingType, __VA_ARGS__) /// Stringizes its second argument. The first argument is not used - it is there /// only because `_ENUM_PP_MAP` expects it. #define _ENUM_STRINGIZE_SINGLE(ignored, expression) #expression /// Stringizes each of its arguments. #define _ENUM_STRINGIZE(...) \ _ENUM_PP_MAP(_ENUM_STRINGIZE_SINGLE, ignored, __VA_ARGS__) /// Symbols that end a constant name. Constant can be defined in several ways, /// for example: /// ~~~{.cc} /// A /// A = AnotherConstant /// A = 42 /// A=42 /// ~~~ /// These definitions are stringized in their entirety by `_ENUM_STRINGIZE`. /// This means that in addition to the actual constant names, the raw `_names` /// arrays potentially contain additional trailing symbols. `_ENUM_NAME_ENDERS` /// defines an array of symbols that would end the part of the string that is /// the actual constant name. Note that it is important that the null terminator /// is implicitly present in this array. #define _ENUM_NAME_ENDERS "= \t\n" /// Compile-time function that determines whether a character terminates the /// name portion of an enum constant definition. /// /// Call as `_endsName(c)`. /// /// @param c Character to be tested. /// @param index Current index into the `_ENUM_NAME_ENDERS` array. /// @return `true` if and only if `c` is one of the characters in /// `_ENUM_NAME_ENDERS`, including the implicit null terminator in that /// array. constexpr bool _endsName(char c, size_t index = 0) { return // First, test whether c is equal to the current character in // _ENUM_NAME_ENDERS. In the case where c is the null terminator, this // will cause _endsName to return true when it has exhausted // _ENUM_NAME_ENDERS. c == _ENUM_NAME_ENDERS[index] ? true : // If _ENUM_NAME_ENDERS has been exhausted and c never matched, return // false. _ENUM_NAME_ENDERS[index] == '\0' ? false : // Otherwise, go on to the next character in _ENUM_ENDERS. _endsName(c, index + 1); } constexpr char _toLowercaseAscii(char c) { return c >= 0x41 && c <= 0x5A ? c + 0x20 : c; } /// Compile-time function that matches a stringized name (with potential /// trailing spaces and equals signs) against a reference name (a regular /// null-terminated string). /// /// Call as `_namesMatch(stringizedName, referenceName)`. /// /// @param stringizedName A stringized constant name, potentially terminated by /// one of the symbols in `_ENUM_NAME_ENDERS` instead of a null terminator. /// @param referenceName A name of interest. Null-terminated. /// @param index Current index into both names. /// @return `true` if and only if the portion of `stringizedName` before any of /// the symbols in `_ENUM_NAME_ENDERS` exactly matches `referenceName`. constexpr bool _namesMatch(const char *stringizedName, const char *referenceName, size_t index = 0) { return // If the current character in the stringized name is a name ender, // return true if the reference name ends as well, and false otherwise. _endsName(stringizedName[index]) ? referenceName[index] == '\0' : // The current character in the stringized name is not a name ender. If // the reference name ended, then it is too short, so return false. referenceName[index] == '\0' ? false : // Neither name has ended. If the two current characters don't match, // return false. stringizedName[index] != referenceName[index] ? false : // Otherwise, if the characters match, continue by comparing the rest of // the names. _namesMatch(stringizedName, referenceName, index + 1); } constexpr bool _namesMatchNocase(const char *stringizedName, const char *referenceName, size_t index = 0) { return _endsName(stringizedName[index]) ? referenceName[index] == '\0' : referenceName[index] == '\0' ? false : _toLowercaseAscii(stringizedName[index]) != _toLowercaseAscii(referenceName[index]) ? false : _namesMatchNocase(stringizedName, referenceName, index + 1); } template constexpr UnderlyingType _findMinLoop(const UnderlyingType *values, size_t valueCount, size_t index, UnderlyingType best) { return index == valueCount ? best : values[index] < best ? _findMinLoop(values, valueCount, index + 1, values[index]) : _findMinLoop(values, valueCount, index + 1, best); } template constexpr UnderlyingType _findMin(const UnderlyingType *values, size_t valueCount) { return _findMinLoop(values, valueCount, 1, values[0]); } template constexpr UnderlyingType _findMaxLoop(const UnderlyingType *values, size_t valueCount, size_t index, UnderlyingType best) { return index == valueCount ? best : values[index] > best ? _findMaxLoop(values, valueCount, index + 1, values[index]) : _findMaxLoop(values, valueCount, index + 1, best); } template constexpr UnderlyingType _findMax(const UnderlyingType *values, size_t count) { return _findMaxLoop(values, count, 1, values[0]); } static inline const char * const* _processNames(const char * const *rawNames, size_t count) { // Allocate the replacement names array. const char **processedNames = new const char*[count]; if (processedNames == nullptr) return nullptr; // Count the number of bytes needed in the replacement names array (an upper // bound). size_t bytesNeeded = 0; for (size_t index = 0; index < count; ++index) bytesNeeded += std::strlen(rawNames[index]) + 1; // Allocate memory for the string data. char *nameStorage = new char[bytesNeeded]; if (nameStorage == nullptr) { delete[] processedNames; return nullptr; } // Trim each name and place the result in storage, then save a pointer to // it. char *writePointer = nameStorage; for (size_t index = 0; index < count; ++index) { const char *nameEnd = std::strpbrk(rawNames[index], _ENUM_NAME_ENDERS); size_t symbolCount = nameEnd == nullptr ? std::strlen(rawNames[index]) : nameEnd - rawNames[index]; std::strncpy(writePointer, rawNames[index], symbolCount); processedNames[index] = writePointer; writePointer += symbolCount; *writePointer = '\0'; ++writePointer; } return processedNames; } template class _GeneratedArrays; } // namespace _enum #define _ENUM_ARRAYS(EnumType, IntegralType, ...) \ enum class EnumType : IntegralType { __VA_ARGS__ }; \ \ namespace _enum { \ \ template <> \ class _GeneratedArrays { \ protected: \ using Integral = IntegralType; \ using Enumerated = EnumType; \ \ public: \ constexpr static const char* name = #EnumType; \ \ protected: \ constexpr static Enumerated _value_array[] = \ { _ENUM_EAT_ASSIGN(Enumerated, __VA_ARGS__) }; \ \ constexpr static const char *_name_array[] = \ { _ENUM_STRINGIZE(__VA_ARGS__) }; \ }; \ \ } #define _ENUM_NOT_FOUND ((size_t)-1) namespace enum_ { template class traits : public _enum::_GeneratedArrays { protected: using _arrays = _enum::_GeneratedArrays; using _arrays::_value_array; using _arrays::_name_array; public: using typename _arrays::Enumerated; using typename _arrays::Integral; constexpr static const size_t size = sizeof(_value_array) / sizeof(Enumerated); static_assert(size > 0, "no constants defined in enum type"); constexpr static const Enumerated first = _value_array[0]; constexpr static const Enumerated last = _value_array[size - 1]; constexpr static const Enumerated min = _enum::_findMin(_value_array, size); constexpr static const Enumerated max = _enum::_findMax(_value_array, size); constexpr static const size_t span = (Integral)max - (Integral)min + 1; constexpr static Integral to_integral(Enumerated value) { return (Integral)value; } constexpr static const Enumerated from_integral(Integral value) { return _value_array[_from_int_loop(value, true)]; } constexpr static const Enumerated from_integral_unchecked(Integral value) { return (Enumerated)value; } static const char* to_string(Enumerated value) { _processNames(); for (size_t index = 0; index < size; ++index) { if (_value_array[index] == value) return _processedNames[index]; } throw std::domain_error("Enum::_to_string: invalid enum value"); } constexpr static const Enumerated from_string(const char *name) { return _value_array[_from_string_loop(name, true)]; } constexpr static const Enumerated from_string_nocase(const char *name) { return _value_array[_from_string_nocase_loop(name, true)]; } constexpr static bool is_valid(Integral value) { return _from_int_loop(value, false) != _ENUM_NOT_FOUND; } constexpr static bool is_valid(const char *name) { return _from_string_loop(name, false) != _ENUM_NOT_FOUND; } constexpr static bool is_valid_nocase(const char *name) { return _from_string_nocase_loop(name, false) != _ENUM_NOT_FOUND; } protected: static const char * const *_processedNames; static void _processNames() { if (_processedNames == nullptr) _processedNames = _enum::_processNames(_name_array, size); } static const char* _getProcessedName(size_t index) { _processNames(); return _processedNames[index]; } using _ValueIterable = _enum::_Iterable>; using _NameIterable = _enum::_Iterable>; friend _enum::_ValueIterator; friend _enum::_NameIterator; public: static const _ValueIterable values; static const _NameIterable names; protected: constexpr static size_t _from_int_loop(Integral value, bool throw_exception, size_t index = 0) { return index == size ? (throw_exception ? throw std::runtime_error( "enum_::traits::from_integral: invalid integer value") : _ENUM_NOT_FOUND) : (Integral)_value_array[index] == value ? index : _from_int_loop(value, throw_exception, index + 1); } constexpr static size_t _from_string_loop(const char *name, bool throw_exception, size_t index = 0) { return index == size ? (throw_exception ? throw std::runtime_error( "enum_::traits::from_string: invalid string argument") : _ENUM_NOT_FOUND) : _enum::_namesMatch(_name_array[index], name) ? index : _from_string_loop(name, throw_exception, index + 1); } constexpr static size_t _from_string_nocase_loop(const char *name, bool throw_exception, size_t index = 0) { return index == size ? (throw_exception ? throw std::runtime_error( "enum::traits::_from_string_nocase: " "invalid string argument") : _ENUM_NOT_FOUND) : _enum::_namesMatchNocase(_name_array[index], name) ? index : _from_string_nocase_loop(name, throw_exception, index + 1); } }; template constexpr typename traits::Integral to_integral(Enum value) { return traits::to_integral(value); } template constexpr Enum from_integral(typename traits::Integral value) { return traits::from_integral(value); } template constexpr Enum from_integral_unchecked(typename traits::Integral value) { return traits::from_integral_unchecked(value); } template const char* to_string(Enum value) { return traits::to_string(value); } template constexpr Enum from_string(const char *name) { return traits::from_string(name); } template constexpr Enum from_string_nocase(const char *name) { return traits::from_string_nocase(name); } } // namespace enum_ #define _ENUM_GLOBALS(EnumType) \ namespace _enum { \ \ constexpr _GeneratedArrays::Enumerated _ENUM_WEAK \ _GeneratedArrays::_value_array[]; \ \ constexpr const char * _ENUM_WEAK \ _GeneratedArrays::_name_array[]; \ \ } \ \ template <> \ constexpr enum_::traits::_ValueIterable _ENUM_WEAK \ enum_::traits::values{}; \ \ template <> \ constexpr enum_::traits::_NameIterable _ENUM_WEAK \ enum_::traits::names{}; \ \ template <> \ const char * const * _ENUM_WEAK enum_::traits::_processedNames = \ nullptr; #define ENUM(EnumType, Integral, ...) \ _ENUM_ARRAYS(EnumType, Integral, __VA_ARGS__); \ _ENUM_GLOBALS(EnumType);