C++11 scoped enumerators (enum class
syntax) do not convert to integers so they cannot be used directly as array indexes.
What\'s the best way to get th
Why make it harder than it needs to be if your enumeration is consecutive?
enum class days
{
monday,
tuesday,
wednesday,
thursday,
friday,
saturday,
sunday,
count
};
....
const auto buffer_size = static_cast< std::size_t >( days::count );
char buffer[ buffer_size ];
buffer[ static_cast< std::size_t >( days::monday ) ] = 'M';
Or if you must use templated functions...
template< class enumeration >
constexpr std::size_t enum_count() noexcept
{
static_assert( std::is_enum< enumeration >::value, "Not an enum" );
return static_cast< std::size_t >( enumeration::count );
}
template< class enumeration >
constexpr std::size_t enum_index( const enumeration value ) noexcept
{
static_assert( std::is_enum< enumeration >::value, "Not an enum" );
return static_cast< std::size_t >( value )
}
...
char buffer[ enum_count< days >() ];
buffer[ enum_index( days::monday ) ] = 'M';
I'm implementing a combination of DrTwox's solution plus the type safety of Potatoswatter's solution. Enumeration class must be explicitly defined to allow indexing, with size() also defined:
#include <iostream>
template< typename T >
class EnumClassTraits;
struct EnumClassTraitIndexing {
static constexpr bool does_index = true;
};
template<typename T>
constexpr
typename std::enable_if<EnumClassTraits<T>::does_index,
typename std::underlying_type<T>::type>::type enum_size() noexcept {
return EnumClassTraits<T>::size();
}
template<typename T>
typename std::enable_if<EnumClassTraits<T>::does_index,
typename std::underlying_type<T>::type>::type enum_index(T enum_key) noexcept {
return static_cast<typename std::underlying_type<T>::type>(enum_key);
}
enum class Days {Mon, Tue, Wed, Thu, Fri, Sat, Sun};
template<>
struct EnumClassTraits<Days> : EnumClassTraitIndexing {
static constexpr std::underlying_type<Days>::type size() {
return static_cast<std::underlying_type<Days>::type>(Days::Sun)+1;
}
};
int main(int argc, char* argv[]) {
Days days[enum_size<Days>()] = {Days::Mon, Days::Tue, Days::Wed, Days::Thu, Days::Fri, Days::Sat, Days::Sun};
const char* days_to_string[enum_size<Days>()] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
for (auto day : days) {
std::cout << days_to_string[enum_index(day)] << std::endl;
}
}
The original question related to using the enumeration as an array index. Instead of trying to turn the enumeration into an index for an array, build an array that accepts the enumeration as its index:
template <typename ValueType, typename Enumeration,
Enumeration largest_enum = Enumeration::Count,
int largest = static_cast <int> (largest_enum)>
class EnumeratedArray {
ValueType underlying [static_cast <int> (largest_enum)];
public:
using value_type = ValueType;
using enumeration_type = Enumeration;
EnumeratedArray () {
for (int i = 0; i < largest; i++) {
underlying [i] = ValueType {};
}
}
inline ValueType &operator[] (const Enumeration index) {
assert (static_cast <int> (index) >= 0 && static_cast <int> (index) < largest);
return underlying [static_cast <const int> (index)];
}
inline const ValueType &operator[] (const Enumeration index) const {
assert (static_cast <int> (index) >= 0 && static_cast <int> (index) < largest);
return underlying [static_cast <const int> (index)];
}
};
So now, with the earlier ducks example:
enum class ducks { huey, dewey, louie, count };
EnumeratedArray<double, ducks, ducks::count> duck_height;
duck_height [ducks::huey] = 42.0;
Had ducks values been capitalized differently, the size could default:
enum class Ducks { Huey, Dewey, Louie, Count };
EnumeratedArray<double, Ducks> duck_height;
duck_height [Ducks::Huey] = 42.0;
In addition to avoiding enum contortions, since the to-index conversion is hidden in the implementation, the enum doesn't risk erroneously becoming an integer at other points in your code, nor can you inadvertently index the array via integer.
EnumeratedArray is used in pianod2, in src/common. A more extensive version there includes template magic to only explicitly default-initialize plain-old datatypes, a constructor to initialize all elements to a specified value, and doc comments.
This is my current favorite. Overload unary operator+
and operator++
to explicitly convert to integral type and increment within the enumerated type, respectively.
Using an enumeration_traits
template, overloads can be activated rather than copying boilerplate code. But the boilerplate is just a couple one-liners.
Library code (templates, see below for non-template alternative):
template< typename e >
struct enumeration_traits;
struct enumeration_trait_indexing {
static constexpr bool does_index = true;
};
template< typename e >
constexpr
typename std::enable_if< enumeration_traits< e >::does_index,
typename std::underlying_type< e >::type >::type
operator + ( e val )
{ return static_cast< typename std::underlying_type< e >::type >( val ); }
template< typename e >
typename std::enable_if< enumeration_traits< e >::does_index,
e & >::type
operator ++ ( e &val )
{ return val = static_cast< e >( + val + 1 ); }
User code:
enum class ducks { huey, dewey, louie, count };
template<> struct enumeration_traits< ducks >
: enumeration_trait_indexing {};
double duck_height[ + ducks::count ];
Boilerplate code (if not using library, follows enum
definition):
int operator + ( ducks val )
{ return static_cast< int >( val ); }
ducks &operator ++ ( ducks &val )
{ return val = static_cast< ducks >( + val + 1 ); }
Scoped enumerator syntax also works on unscoped (non enum class
) enumerations, which do implicitly convert to int
. Hiding the enumeration inside a class or namespace and importing it with typedef
or using
makes it pseudo-scoped.
But if multiple enumerations go into the same namespace, the enumerator names may collide, so you might as well use a class (or many namespaces).
struct ducks_enum {
enum ducks { huey, dewey, louie, count };
};
typedef ducks_enum::ducks ducks;
double duck_height[ ducks::count ]; // C++11
double duck_weight[ ducks_enum::count ]; // C++03
This has some benefits. It works with C++03, but only with syntax ducks_enum::count
. The enumerators are unscoped inside the struct, and it can be used as a base for any class that makes frequent use of the enumerators.
Alternatively you can replace your array
with a map
, which also means you can get rid of the maximum enum like count
:
enum class days
{
monday,
tuesday,
wednesday,
thursday,
friday,
saturday,
sunday
};
int main(int argc, char* argv[])
{
std::map<days, const char*> days_to_string =
{{days::monday, "Monday"},
{days::tuesday, "Tuesday"},
{days::wednesday, "Wednesday"},
{days::thursday, "Thursday"},
{days::friday, "Friday"}};
for (auto day : days)
{
std::cout << days_to_string[day] << std::endl;
}
}