Click here to Skip to main content
Click here to Skip to main content

Enumerator Lists and Enumerated Arrays

, 4 Oct 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
Establishing a strong binding between enumerations and arrays

Table of Contents

Introduction

During my work, I often came around the following situation. There is an enumeration declared in a header file, let's say:

enum EWeekDay {
    DAY_SUNDAY,
    DAY_MONDAY,
    DAY_TUESDAY,
    DAY_WEDNESDAY,
    DAY_THURSDAY,
    DAY_FRIDAY,
    DAY_SATURDAY,
    DAY_LAST
};

In another code location, typically a source file - as it is only needed at local scope, the enumeration is mapped to an array of arbitrary type. In the following example, a string array is used as this happens often enough:

const char* const sWeekDay[] = 
{
    "Sunday",
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday",
    "Saturday"
};

The enumerators' string representations may be easily accessed, e.g., by writing sWeekDay[DAY_TUESDAY]. Unfortunately, this approach is entirely unsafe as it does not enforce updates of sWeekDay when changes occur to EWeekDay:

  • If enumerators are added or removed from the enumeration, the update of the array is not enforced. This will result in index out of bounds problems when an enumerator is added and enumerator / string mismatches when one is removed.
  • Swapping enumerators (e.g. placing DAY_SUNDAY at the end of the week) will result in enumerator / string mismatches.
  • The integral values representing the enumerator must be a continuous range, otherwise the conversion to array indexes will not be possible.

Besides that, the array is not directly linked to the enumeration. It would be possible to pass an enumerator of another enumeration as index or to directly use integers.

It is true that very simple steps can be taken to improve the safety of the enumerated array:

  • Code comments can be used to tell the developer about other code locations that need changing.
  • The array may be declared as const char* const sWeekDay[DAY_LAST]. This would not inform the programmer that he will have to change the array, but at least it would automatically size the array and initialize missing values with 0. Runtime-code then could assert on 0-pointers.
  • A static assertion may be used to verify the length of the enumeration against the length of the array:
    static_assert(sizeof(sWeekDay) / sizeof(sWeekDay[0]) == DAY_LAST,
        "array size mismatch!");
    

    This does not look nice, but it would inform about sizing mismatches at compile time.

  • A std::map initialized from a range of std::pairs may be used to combine enumeration and array (shown below).
  • Another option is to use boost::unordered_map and boost::unordered_map::assign::map_list_of.
  • Using a enum2str<...> template and probably a dozen more solutions.

The approach using std::map would be:

std::pair<EWeekDay, const char* const> WeekDayNames[] = 
{
    std::make_pair(DAY_SUNDAY,      "Sunday"),
    std::make_pair(DAY_MONDAY,      "Monday"),
    std::make_pair(DAY_TUESDAY,     "Tuesday"),
    std::make_pair(DAY_WEDNESDAY,   "Wednesday"),
    std::make_pair(DAY_THURSDAY,    "Thursday"),
    std::make_pair(DAY_FRIDAY,      "Friday"),
    std::make_pair(DAY_SATURDAY,    "Saturday")
};

const std::map<EWeekDay, const char* const> DayMap(&WeekDayNames[0], 
    &WeekDayNames[DAY_LAST]);

Somewhere in the source file, the following static assertion can be placed:

static_assert(sizeof(WeekDayNames) / sizeof(WeekDayNames[0]) == DAY_LAST,
    "array size mismatch!");

As I declared WeekDayNames as const, it is not possible to use operator[]. However the following call will work:

std::cout << DayMap.find(DAY_TUESDAY)->second;

Let's see where we are:

  • The enumerators and their values are tightly coupled so the order of enumerators and their integer representations will not interfere with the result.
  • It is not possible to pass an integer or enumerator of another enumeration to std::map::find(...).
  • The size of the enumeration and array must match for the code to compile.
  • Still, the static assertion safeguarding the map size will only work if the enumerated range starts at 0, runs continuously (i.e., 0, 1, 2, 3, ...) and ends with a terminating enumerator (DAY_LAST).

Starting from this, I thought about a way to improve usage safety further by moving potential problems from run-time exceptions to compile-time errors and also introduce some new features to enumerations and enumerated arrays. Of course, I came to use template metaprogramming (TMP) very quickly.

Background

My work is partly based on the Andrei Alexandrescu's book "Modern C++ Design", chapter 3 "Typelists" where the idea and implementation of type lists and tuples is discussed. Information about iterator implementation is taken from Bjarne Stroustrup's book, "The C++ Programming Language", " Chapter 19 Iterators and Allocators".

While the original version of this article was stand-alone, this revised version is based on the templates provided in my CodeProject article Static Value Lists which serves as implementation base for the enumerator lists discussed in the following.

This article does not intend to explain the solution approach used but is an introduction to and reference for the provided templates. The technical intricacies of static value lists are discussed in detail in the referred article - in comparison, the code introduced here is self-explaining.

Some special terminology is used (Please also refer to the "Static Value Lists" article):

  • Enumerator Lists
    A static value list that is created on base of a series of enumerators from one enumeration. Later on, this type can be queried for information about itself. As already hinted, enumerator lists are static value lists that contain enumerators of a certain enumeration.
  • Static Index Array
    An array that is indexed by items from a static value list. As a result, the static index array has exactly as many elements as the static value list has values. Values and array items are closely linked as the value index of in the static value list is used as item index in the static index array.
  • Enumerated Arrays
    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.

Using the Code

The provided templates are implemented in the lobster project in header files only. It has the following directory structure:

  • lobster: A header-only library providing a common namespace. Within the folder, header files are contained that can be externally referenced. E.g. including "static_list.h" will load all files required for working with static lists. Therefore this folder is named as additional include folder in the sample code.
    • static_index_array: The array template namespace that will be used to define enumerated arrays in the following
    • static_list: The namespace providing the templates for implementing static value lists and enumerator lists
    • ...: Other namespaces that are not discussed here
  • enm_array_sample
    • source: Contains the sample code files
    • gcc: Batch file for compiling the sample with gcc. The output will be an executable in the same folder
    • msvc: Workspace and project files for VS2010

The namespace used for enumerated arrays is lobster::static_index_array, it has to be loaded by including "static_index_array.h" from the "lobster" folder. It is intended to provide a using directive to the lobster namespace and then use static_index_array as prefix to the templates explicitly. Otherwise, there may be naming collisions with STL or other code. The sample code however sets the using directive directly.

The coding convention in the lobster library is based on the STL as much as possible to me, however no coding convention is applied to the sample code.

The library and sample code is written and tested in VC++ 2010 and GCC 4.5.2, but may work on other C++ compilers providing the TR1 extensions as well (I have not tested this).

The sample project contains the "enm_demo.cpp" file providing the examples discussed here.

Enumerator Lists

For defining enumerator lists, the lobster library "static_list.h" header has to be included (Make sure that the folder "lobster" is referenced appropriately):

#include "static_list.h"

An enumerator list is declared in the same way as any other static value list. The most primitive approach is by using the static_list::list_item template. This is a recursive template so for each enumerator, one static_list::list_item definition is used. The enumerator values can be set explicitly which is ok as long as they are individual for each entry.

The enumerator list tail must be explicitly set by the static_list::list_tail template, it defines the enumeration used and must be the provider of all enumerators.

typedef list_item<list_item<list_item<list_tail<EWeekDay>, 
    DAY_MONDAY>, DAY_TUESDAY>, DAY_WEDNESDAY> my_day_list;

As this is rather cumbersome, there are helper templates static_list::list_1 to static_list::list_10 which allow to construct the lists more easily. Still, the tail must be set as first template parameter manually as it is possible to chain enumerator list definitions:

typedef list_5<list_tail<EWeekDay>, DAY_MONDAY, DAY_TUESDAY, 
    DAY_WEDNESDAY, DAY_THURSDAY, DAY_FRIDAY>::value workdays;
		
typedef list_2<workdays, DAY_SATURDAY, DAY_SUNDAY>::value weekdays;

The static value list offers the following run-time functionality:

list_item<...> Member

Description
static int index_of(value_type) The index of an enumerator in the list is returned (Due to the template instantiation, value_type will be defined as EWeekDay in the previous example. If the enumerator is not part of the list, -1 is returned.
static enum_type at(int) The value at the provided index is returned. If the index is invalid, a std::out_of_range exception is thrown.
list_item<...>::const_iterator An input iterator for the enumerator list is provided. As the values are defined at compile time, only const-access to its values is possible. An example for using the iterator is given in "enm_demo.cpp" when calling the iterator_test(...) function.
static list<...>::const_iterator begin() An iterator pointing to the first item in the list is returned.
static list<...>::const_iterator end() An iterator pointing behind the last item in the list is returned.

At compile-time, the template class static_list::value_index_of<list, enumerator>::value can be used to query the index of a value (enumerator in this case) in the list. If the value is not part of the list, -1 is returned. It is mostly helpful when testing for the presence of an enumerator in a list at compile-time.

Enumerated Arrays

The files required for implementing enumerated arrays as well as static value lists are referred in "static_index_array.h".

#include "static_index_array.h"

A static index array can be seen as a enumerated array generalization where not only enumerator lists but also other static value lists may serve as index provider.

An enumerated array is defined by using the lobster::static_index_array::array class template. The static value list defining the enumerator list is simply passed as a template parameter:

typedef array<weekdays, std::string> weekday_string_array;

Aside from the standard constructor, there are two other constructors available for initializing the enumerated array:

A constructor similar to template <class InputIterator> map(InputIterator first, InputIterator last ...) is provided to allow initialization in the same way as it is done in the std::map sample:

weekday_string_array wsa(&WeekDayNames[0], &WeekDayNames[DAY_LAST])

Please note that dereferencing the input iterator must give access to a std::pair whose first value must be an enumerator of the enumeration used and whose second type must be implicitly convertible to the enumerated array value type. In this case, C-style strings are converted into std::string.

Alternatively, the std::pair array can be passed directly:

weekday_string_array wsa(WeekDayNames);

In this variant, the parameter must be a C-style array of std::pair<value_type, any_type>.

The static_index_array::array template is const-correct, so it is possible to use the const-qualifier in the declaration. This will prevent the contained values to be modified later on.

static_index_array::array provides the following methods:

static_index_array::array<...> Methods:

Description
value_type& value<key_type>();
const value_type& value<key_type>() const;
Access is granted to the value associated with an enumerator (key_type is defined as the proper enumeration). The enumerator is evaluated at compile-time. If the enumerator is not part of this static_index_array::array's enumerator list, the method template will not compile. However, the compiler error may not be very helpful.
value_type& value(key_type);
const value_type& value(key_type) const;
Access is granted to the value associated with an enumerator. The enumerator is evaluated at run-time, so an exception will be thrown if the enumerator is not part of this static_index_array::array's enumerator list.

Default Values for Enumerated Arrays

Instead of causing compile- or run-time errors, it is possible to use a default value as fallback. For this, the static_index_array::defaulting_array template class is used as an adapter for the enumerated array. It provides the same implicit interface (see table above) as static_index_array::array and can be used as drop-in replacement.

typedef defaulting_array<weekdays, std::string> weekday_default_array;

The methods of this class will grant access to the default value if provided with an enumerator not in the enumerator list. The initialization of the default value is possible by extending the initialization array:

std::pair<EWeekDay, char const * const> WeekDayNames_Def[] = 
{
    std::make_pair(DAY_SUNDAY,      "Sunday"),
    std::make_pair(DAY_MONDAY,      "Monday"),
    std::make_pair(DAY_TUESDAY,     "Tuesday"),
    std::make_pair(DAY_WEDNESDAY,   "Wednesday"),
    std::make_pair(DAY_THURSDAY,    "Thursday"),
    std::make_pair(DAY_FRIDAY,      "Friday"),
    std::make_pair(DAY_SATURDAY,    "Saturday"),
    std::make_pair(DAY_LAST,        "#error")
};

const weekday_default_array wda(WeekDayNames_Def);

Summary

The enumerated array construct provides the following benefits:

  • The enumeration does not need to provide enumerators of continuous values.
  • The enumerators and their values are tightly coupled.
  • Multiple enumerator lists can be generated from one enumeration.
  • The size of the enumeration and WeekDayNames must match for the code to compile when using the array constructor.
  • Compile-time access to the enumerated array is granted, so that wrong enumerators will cause a compiler-error.
  • Run-time access is also granted so the value field which is to be accessed may be determined at run-time, too.

TMP code is certainly delays compile-time is not easily optimized for run-time. efficiency. However I don't think that this is a problem as the devices offered here are supposedly only useful for small enumerations.

The initializing static_index_array::array constructors do not check the completeness of the provided pairs. It would be possible to trick it by providing the same enumerator twice.

Points of Interest

I started this project as my first attempt of using TMP in more than a minimalistic scale. It turned out to be time-consuming and hard to debug (not to mention the compiler crashes due to my mistakes or the compilers).

The things one can do with TMP are amazing and it plays an important role in basic library development, but it is not something I would encourage during application development. It is also hard to document components based on templates as they do not provide clear interfaces.

History

  • 2011/10/03 Version 2
    • Update for VS2010 and GCC 4.5.2
  • 2011/06/30 Version 1

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Doc Lobster
Software Developer
Germany Germany
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberHamlet Escaño1-Jul-11 11:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411028.1 | Last Updated 4 Oct 2011
Article Copyright 2011 by Doc Lobster
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid