I think I'd encode this into something on the order of a state machine--a table holding the conditions and actions associated with each, and a tiny bit of code to walk through those to carry out the tests:
#include <iostream>
#include <functional>
#ifdef TEST
bool do_n, do_a, do_up;
bool success;
bool T = true;
void run_do_up() { std::cout << "did up\n"; }
void run_do_a() { std::cout << "did a\n"; }
void run_do_n() { std::cout << "did n\n"; }
#endif
struct conditional_action {
bool const &condition;
std::function <void()> action;
template <class F>
conditional_action(bool const &b, F &a) : condition(b), action(a) {}
conditional_action() : condition(T), action([&]{success = false; }) {}
};
conditional_action actions[4][3] {
{ { do_up, run_do_up }, { do_a, run_do_a }, { do_n, run_do_n } },
{ { do_n, run_do_n }, { do_a, run_do_a } },
{ { do_a, run_do_a }, { do_n, run_do_n } }
};
void exec(size_t x) {
for (auto const &a : actions[x])
if (a.condition) {
a.action();
return;
}
}
#ifdef TEST
int main() {
do_n = true;
exec(0);
do_a = true;
exec(2);
}
#endif
Depending on viewpoint, you might want to add a { T, [&]{success = false; }}
to the end of each row in the actions
table. That's what the default ctor will fill those entries with anyway, but there's a fair argument to be made that since it was explicit in the original logic, it should remain explicit here as well.
There is probably at least a little argument to be made that this is kind of a borderline case. A case where there were more conditions and actions would favor this technique more strongly. Whether really it makes sense when you have only three conditions and actions may be open to a little more question.