Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

array indexing (converting to integer) with scoped enumeration

Tags:

c++

arrays

enums

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 the benefit of scoping when using them in this fashion?

I've provided a couple answers, but please add more ideas!

like image 552
Potatoswatter Avatar asked Oct 17 '12 05:10

Potatoswatter


People also ask

Can enum be used in array index?

It is perfectly normal to use an enum for indexing into an array. You don't have to specify each enum value, they will increment automatically by 1. Letting the compiler pick the values reduces the possibility of mistyping and creating a bug, but it deprives you of seeing the values, which might be useful in debugging.

What is a scoped enumeration?

Scoped enums (enum class/struct) are strongly typed enumerations introduced in C++11. They address several shortcomings of the old C-style (C++98) enums, mainly associated with type-safety and name collisions.

What is an enumerated array?

A static index array that is created on base of an enumerator list. The values behind the enumerators may be accessed at run- and compile-time.

What does enum class do in C++?

C++11 has introduced enum classes (also called scoped enumerations), that makes enumerations both strongly typed and strongly scoped. Class enum doesn't allow implicit conversion to int, and also doesn't compare enumerators from different enumerations. To define enum class we use class keyword after enum keyword.


Video Answer


3 Answers

Solution 1: Operator overloading.

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 ); }

Solution 2: Manual scoping.

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.

like image 62
Potatoswatter Avatar answered Oct 26 '22 23:10

Potatoswatter


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';
like image 42
x-x Avatar answered Oct 26 '22 22:10

x-x


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.

like image 31
Perette Avatar answered Oct 27 '22 00:10

Perette