"Elegant" is in the eye of the beholder; but I have found that using macros to generate code sequences handling enums greatly reduces redundancy and the chance for errors. It is one of the few justified cases (beyond conditional compilation and includes) to use the preprocessor. As is often the case this pays more dividends in large and distributed projects.
The idea is to generate the enumeration itself as well as switch cases and containers such as maps or arrays from the same text, which must be in a separate file so that the same copy can be included several times. That makes it physically impossible to have enum members without an associated action. Adding another state is as trivial as adding a line to enum-actions.h; all uses like map entries, switch cases or else-if chains that are generated from that list are automatically adapted. These places still need to be recompiled because of the include dependency, but they don't need to be touched.
Here is the file which contains the enum member names, values and associated actions (which must be function names or functors at the time the switch/case is generated). I gave it a .h suffix because it will be included, even though it is not grammatical C++; one could also give it a .txt suffix.
enum-actions.h
// A list of enum identifiers with associated values
// and actions usable in macro definitions.
// This macro is used to construct both the actual enum
// as well as the cases in the action switch. This way
// it is impossible to have enum members without an associated action.
// There is no "real" enum definition elsewhere; this is it.
ENUM_ACTION(S1, 2, action1)
ENUM_ACTION(S2, 23, action2)
ENUM_ACTION(S3, 997, no_action)
stateE.h
// Define the states enum from the macro list of enum/action pairs.
// The last member will have a trailing comma as well, which
// is permitted by the C++ grammar exactly for this use case of code generation.
enum stateE
{
# define ENUM_ACTION(state, value, action) state = value,
# include "enum-actions.h"
# undef ENUM_ACTION
};
associative-enum.cpp
#include <iostream>
#include "stateE.h"
// Dummy actions for states
void action1() { std::cout << __func__ << "\n"; }
void action2() { std::cout << __func__ << "\n"; }
// pseudo action when nothing should happen
void no_action() { std::cout << __func__ << "\n"; }
/// Perform the action associated with the state. This is done with a
/// switch whose cases are constructed from the list
/// in enum-actions.h.
void actOnState(stateE stateArg)
{
switch (stateArg)
{
# define ENUM_ACTION(state, value, action) case state: action(); break;
# include "enum-actions.h"
# undef ENUM_ACTION
}
}
int main()
{
actOnState(S1);
actOnState(S2);
actOnState(S3);
}
Sample session:
$ g++ -Wall -o associative-enum associative-enum.cpp && ./associative-enum
action1
action2
no_action
break
ofS3
is not needed. – Trienniumcase
clauses "fall-through" so you could group all the unused cases together with a singlebreak
(and a comment explaining this to your future self). – Egressdefault
is unfortunately necessary, and should not be empty (instead it should raise an assertion failure). The reason is that a variable of your enum can contain other values than the ones you’ve declared. – Melodiousdefault
. – Jarlen