Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Type-Safe Array Indexing with Enums
Use enum values as descriptive, type-safe array indices.
Array indexing and length using enumerators
Integer indices lack semantic meaning, making code harder to understand and maintain. Consider a game inventory system:
#include <vector>
int main()
{
std::vector inventory { 15, 8, 42 };
inventory[1] = 12; // What does index 1 represent?
return 0;
}
What item is at index 1? Without context, this code is cryptic.
Using enumerators for self-documenting indices
Unscoped enumerations implicitly convert to integral types, making them perfect for array indices:
#include <vector>
namespace Items
{
enum Type
{
health_potions,
mana_potions,
arrows,
max_items
};
}
int main()
{
std::vector inventory { 15, 8, 42 };
inventory[Items::mana_potions] = 12; // Much clearer!
return 0;
}
Now it's obvious: we're updating the mana potion count. Because enumerators are implicitly constexpr, their conversion to std::size_t isn't considered narrowing.
Using non-constexpr enumerator variables
Enumerators themselves are constexpr, but variables of enumeration type aren't necessarily:
#include <vector>
namespace Items
{
enum Type
{
health_potions,
mana_potions,
arrows,
max_items
};
}
int main()
{
std::vector inventory { 15, 8, 42 };
Items::Type item { Items::mana_potions }; // Non-constexpr
inventory[item] = 12; // May trigger sign conversion warning
return 0;
}
If the enumeration's underlying type is signed (implementation-defined), this might generate warnings. Two solutions:
Option 1: Make the variable constexpr (when possible):
constexpr Items::Type item { Items::mana_potions };
inventory[item] = 12; // No warning
Option 2: Specify an unsigned underlying type:
namespace Items
{
enum Type : unsigned int // Explicitly unsigned
{
health_potions,
mana_potions,
arrows,
max_items
};
}
Using a count enumerator
Add a final enumerator representing the count of preceding enumerators:
#include <iostream>
#include <vector>
namespace Items
{
enum Type
{
health_potions,
mana_potions,
arrows,
// Add new items here
max_items // Automatically equals the count
};
}
int main()
{
std::vector<int> inventory(Items::max_items); // Creates 3-element vector
inventory[Items::arrows] = 42;
std::cout << "Tracking " << Items::max_items << " item types\n";
return 0;
}
The count enumerator automatically updates when you add items. If we add gems before max_items:
namespace Items
{
enum Type
{
health_potions,
mana_potions,
arrows,
gems, // New item
max_items // Now equals 4
};
}
int main()
{
std::vector<int> inventory(Items::max_items); // Now creates 4-element vector
// Everything still works!
return 0;
}
Asserting on array length
When initializing an array with specific values, assert that the size matches your count enumerator:
#include <cassert>
#include <iostream>
#include <vector>
enum Planets
{
mercury,
venus,
earth,
mars,
max_planets
};
int main()
{
std::vector distanceFromSun { 58, 108, 150, 228 }; // Millions of km
assert(std::size(distanceFromSun) == max_planets);
std::cout << "Earth is " << distanceFromSun[earth] << " million km from the sun\n";
return 0;
}
If the number of initializers doesn't match max_planets, the assert catches the error immediately.
Use `static_assert` for constexpr arrays, `assert` for non-constexpr arrays.
Enum classes and array indices
Enum classes don't implicitly convert to integers, requiring explicit casts:
#include <iostream>
#include <vector>
enum class Planets
{
mercury,
venus,
earth,
mars,
max_planets
};
int main()
{
std::vector distanceFromSun { 58, 108, 150, 228 };
// Error: no implicit conversion
std::cout << distanceFromSun[Planets::earth] << '\n';
// OK: explicit cast
std::cout << distanceFromSun[static_cast<int>(Planets::earth)] << '\n';
return 0;
}
While verbose, a helper function improves readability:
#include <iostream>
#include <type_traits>
#include <vector>
enum class Planets
{
mercury,
venus,
earth,
mars,
max_planets
};
// Overload unary + to convert enum class to underlying type
constexpr auto operator+(Planets p) noexcept
{
return static_cast<std::underlying_type_t<Planets>>(p);
}
int main()
{
std::vector distanceFromSun { 58, 108, 150, 228 };
std::cout << distanceFromSun[+Planets::earth] << '\n'; // Cleaner!
return 0;
}
For heavy enum class usage, consider using a regular enum in a namespace instead.
Summary
Enumerators as array indices: Unscoped enumerations implicitly convert to integral types, making them ideal for self-documenting array indices. Using named enumerators like Items::mana_potions instead of magic numbers like 1 makes code much more readable and maintainable.
Count enumerators: Adding a final enumerator (like max_items) that represents the count of preceding enumerators allows automatic tracking of array length. When you add new enumerators, the count updates automatically, making arrays sized with this value adjust accordingly.
Asserting array length: Use assert() or static_assert() to verify that initialized arrays have the expected number of elements. This catches mismatches between initializer counts and enum counts immediately.
Non-constexpr enum variables: Variables of enumeration type (unlike enumerators themselves) may require special handling. Make them constexpr when possible, or specify an unsigned underlying type for the enum to avoid sign conversion warnings.
Enum classes require casting: Enum classes don't implicitly convert to integers, requiring explicit static_cast or helper functions (like overloading unary +) for array indexing. For frequent array indexing, regular enums in namespaces may be more practical.
Using enumerators with arrays combines type safety with readability, creating self-documenting code that's easier to maintain as requirements evolve.
Type-Safe Array Indexing with Enums - Quiz
Test your understanding of the lesson.
Practice Exercises
RPG Inventory System with Enumerators
Create a program that uses enumerators to index a game inventory array. The program manages different item types (health potions, mana potions, arrows, gold) using an enumeration for self-documenting array access.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!