Today, I want to share a little C++ container class with you that has proved useful over the years: const_map. I finally got around to turning it into something reusable, or so I hope. Special thanks go to John Hinke, who provided awesome feedback.
In simple words, const_map is an associated array, just like good ol’ std::map: a mapping from keys to values which allows you to lookup values by keys:
1 2 3 4 |
const char* color_name = color_names[eColorRed]; cout << "Look, Ma, " << eColorRed << " is called " << color_name << endl; |
Contrary to std::map, however, const_map is a read-only container. To set it up, you provide a key/value mapping table to the constructor:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
enum Color { eColorBlack, eColorRed, eColorGreen, eColorBlue, }; // The mapping table. const_map<Color, const char*>::value_type COLOR_NAMES[] = { { eColorRed , "red" }, { eColorGreen , "green" }, { eColorBlue , "blue" }, }; // The const_map. const_map<Color, const char*> color_names(COLOR_NAMES); |
Once a const_map is instantiated, it’s not possible to add or remove key/value pairs anymore:
1 2 3 |
color_names[eColorBlack] = "black"; // Error: const_map is read-only! |
Just like std::map, const_map’s runtime is O(log(n)). Contrary to std::map, the implementation isn’t based on binary trees but on binary search. Hence, the mapping table must be sorted by key in ascending order.
const_map doesn’t do any heap allocations. All it does is maintain two pointers: one to the beginning and another one to the end of the mapping table. Apart form these two pointers, const_map doesn’t need any other state or housekeeping data. Thus, on a 32-bit platform, const_map instances will cost you eight measly bytes of RAM.
Now, I know there are some embedded cheapskates out there to whom even eight bytes is sometimes too big a sacrifice. If you’re so tight on RAM and are willing to forgo the fancy STL-like interface and rather expend a little bit more typing, you can use the static class member function ‘lookup’ which is completely stateless and doesn’t require an instance:
1 2 3 4 5 6 7 8 |
auto iter = const_map<Color, const char*>::lookup(COLOR_NAMES, eColorBlue); EXPECT_EQ("blue", iter->second); // 'lookup' returns a null pointer if no match is found. iter = const_map<Color, const char*>::lookup(COLOR_NAMES, eColorBlack); EXPECT_TRUE(iter == nullptr); |
I’ve mostly used const_map as a replacement for long, unwieldy switch-case abominations. Either to translate values from one data type to another or to determine which action to take based on a given value. In the latter case, my value type was a function pointer or functor.