This is going to be another programming post.
One thing that always annoys me when working on a project in a language like C++ is that when I’m debugging, I’d like to print messages with meaningful names for the enumerated types I’m using.
The classic way to do it is something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <stdio.h> enum blee_t { blee_foo, blee_bar, blee_baz, }; const char *showblee(enum blee_t k) { switch (k) { case blee_foo: return "blee_foo"; case blee_bar: return "blee_bar"; case blee_baz: return "blee_baz"; } return "__unknown__"; } int main(int argc, char*argv[]) { puts(showblee(blee_bar)); } |
Note that I have perhaps too-cleverly left out the break statements because each case returns.
But this has problems:
- repetitive typing
- maintenance. Whenever you change the enum, you have to remember to change the debug function.
It just feels super-clunky.
I made a little class in C++ that I like a bit better because you only have to write the wrapper code once even to use it on a bunch of different enums. Also you can hide the code part in another file and never see or think about it again.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#include <map> #include <string> #include <iostream> // an enum enum events_t { ev_failed = 0, ev_notStarted, ev_yayItWorked, ev_booNoWorky, ev_stillThinking, }; // another enum enum modes_t { mode_frobulate, mode_fizzbuzz, mode_nop, }; template<typename key_t> class messages_c { private: static const std::map<key_t,std::string> messages; public: std::string get(key_t key) { auto it = messages.find(key); if (it == messages.end()) { return "__unknown__ key:" + std::to_string((int)key); } else { return it->second; } } }; template<> const std::map<events_t,std::string> messages_c<events_t>::messages = { { ev_failed, "ev_failed" }, { ev_notStarted, "ev_notStarted" }, { ev_yayItWorked, "ev_yayItWorked" }, { ev_booNoWorky, "ev_booNoWorky" }, { ev_stillThinking, "ev_booNoWorky" }, }; template<> const std::map<modes_t,std::string> messages_c<modes_t>::messages = { { mode_frobulate, "mode_frobulate", }, { mode_fizzbuzz, "mode_fizbuzz", }, { mode_nop, "mode_nop", }, }; int main(int argc, char *argv[]) { messages_c<events_t> ev_strings; messages_c<modes_t> md_strings ; std::cout << ev_strings.get(ev_notStarted) << "\n"; std::cout << md_strings.get(mode_nop) << "\n"; }; |
C++11 lets you initialize those maps pretty nicely, and they are static const, so you don’t have to worry about clobbering them or having multiple copies. But overall, it still blows because you have to type those identifiers no fewer than three times: once in the definition and twice in the printer thing.
Unsatisfactory.
I Googled a bit and learned about how Boost provides some seriously abusive preprocessor macros, including one that can loop. I don’t know what kind of dark preprocessor magic Boost uses, but it works. Here is the template and some macros:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#ifndef __ENUMULATOR_H #define __ENUMULATOR_H #include <map> #include <string> #include <boost/preprocessor.hpp> #define MAKE_ENUM(name,list) \ enum name { \ BOOST_PP_SEQ_ENUM(list) \ } #define POPULATE_ENUMNAMES_ONEROW(r,data,elem) \ { elem, BOOST_PP_STRINGIZE(elem) }, #define POPULATE_ENUMNAMES(name,list) \ template<> \ const std::map<name,std::string> enumnames_c<name>::names = { \ BOOST_PP_SEQ_FOR_EACH(POPULATE_ENUMNAMES_ONEROW,name,list) \ } template<typename key_t> class enumnames_c { private: static const std::map<key_t,std::string> names; public: std::string get(key_t key) { auto it = names.find(key); if (it == names.end()) { return "__unknown__ key:" + std::to_string((int)key); } else { return it->second; } } }; #endif |
And here’s how you use it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include <map> #include <string> #include <iostream> #include <boost/preprocessor.hpp> #include "enumulator.h" // the enumators for our enums, in a slightly weird // parenthesized list form #define EVENTS_LIST (ev_failed) \ (ev_notStarted) \ (ev_yayItWorked) \ (ev_booNoWorky) \ (ev_stillThinking) #define MODES_LIST (mode_frobulate) \ (mode_fizzbuzz) \ (mode_nop) MAKE_ENUM(events_t,EVENTS_LIST); MAKE_ENUM(modes_t,MODES_LIST); POPULATE_ENUMNAMES(events_t, EVENTS_LIST); POPULATE_ENUMNAMES(modes_t, MODES_LIST); int main(int argc, char *argv[]) { enumnames_c<events_t> ev_strings; enumnames_c<modes_t> md_strings ; std::cout << ev_strings.get(ev_notStarted) << "\n"; std::cout << md_strings.get(mode_nop) << "\n"; }; |
Now I only have to list out the enumerators one time! Not bad. However, it obviously only works if you control the enum. If you are importing someone else’s header with the definition, it still has the maintenance problem of the other solutions.
I understand that the C++ template language is Turing-complete, so I’m suspect this can be done entirely with templates and no macros, but I wouldn’t have the foggiest idea how to start. Perhaps one of you do?
You got me here. All I can say is that I know who Alan Turing was.
There was something political I wanted to write about this morning, but I forgot what it was.
Clever to put in a link on the data mining site of faces but no indication of what you are talking about. Having read it, I have an inkling but…. Grammar and form about a language I don’t know. Where are the punch cards?? (I think I have settled on at least one of my “this is how I will troll young people with my stories of walking uphill in the snow”)