====== 2023-08-26 - handling enums in C++ ====== handling ''enum''s in [[wp>C++]] is tricky. but there are good practices that can help you out. here are two from the set. ===== handling all values ===== let's say we have a simple ''enum'' called ''Foo'', with some values. we want to add a ''str()'' helper, that would covert it to sth more human-readable than a number. very common implementation would be: enum class Foo { A, B, C }; // ... auto str(Foo f) { switch(f) { case Foo::A: return "A"; case Foo::B: return "B"; case Foo::C: return "C"; default: return ""; } } looks good, all tests pass. however there is a potential for a problem -- it's not future proof. let's now add ''Foo::D'': enum class Foo { A, B, C, D }; // notice Foo::D is added! // ... auto str(Foo f) { switch(f) { case Foo::A: return "A"; case Foo::B: return "B"; case Foo::C: return "C"; default: return ""; } } looks good, all tests pass... however now there _is_ a problem! new value is not handled and it will return an incorrect value at runtime! there's a neat way of fixing this problem and making code future-proof. compile with ''-Wall -Werror'' (as you anyway should!) and make sure no ''switch'' has any ''default''. eg.: auto str(Foo f) { switch(f) { case Foo::A: return "A"; case Foo::B: return "B"; case Foo::C: return "C"; } return ""; } change looks insignificant, but now if we miss handling 1 value, we get a warning from a compiler and with ''-Werror'' it stops the build right away. c.cpp: In function ‘auto str(Foo)’: c.cpp:11:9: error: enumeration value ‘D’ not handled in switch [-Werror=switch] 11 | switch(f) | ^ cc1plus: all warnings being treated as errors bug is detected at compile time! nice... :) ===== handling some values ===== another common case where ''enum''s are used is adding special functions, that check if value is in a given logical state / group. eg. determining if it's an error: enum class State { Initializing, Running, InputError }; bool isOk(State s) { return s != State::InputError; } looks good, all tests pass. however... you probably already know where it's going. ;) let's consider extending ''State'' with a new error state: enum class State { Initializing, Running, InputError, OutputError }; ...and now ''isOk(State::OutputError) == true''. whoops... a better way of writing these kind of function is to list all ''enum'' values explicitly, and assign ''return''s accordingly, eg.: #include // ... bool isOk(State s) { switch(s) { case State::Initializing: case State::Running: return true; case State::InputError: return false; } assert(!"unknown value"); } now, if states change, we get a proper error: 4.cpp: In function ‘bool isOk(State)’: 4.cpp:13:9: error: enumeration value ‘OutputError’ not handled in switch [-Werror=switch] 13 | switch(s) | ^ cc1plus: all warnings being treated as errors so we know we need to fix it: #include // ... bool isOk(State s) { switch(s) { case State::Initializing: case State::Running: return true; case State::InputError: case State::OutputError: return false; } assert(!"unknown value"); } while writing this kind of functions using ''switch''-''case'' may look a bit odd at first, it has a very nice property of ensuring code being future-proof. as a side effect, it also makes core far easier to read. for comparison just imagine ''isOk()'' where there are 7 "ok" states and 13 "error" states...