0

I have encountered a problem for which I'm unable to find an easily maintainable and readable solution to.

I'm basically writing a "master state machine" I have a node which receives enums from 3 other slave nodes, which independently do their thing and come to a certain state and report it to the master

Slave 1 outputs one of the enums:

enum slave1 {
A,
B
}

Slave 2 outputs one of the enums:

enum slave2 {
1,
2
}

Slave 3 outputs one of the enums:

enum slave3 {
a,
b,
c
}

It is important to note that i don't have any control over the behavior, structure and outputs of the slave nodes

Now, based on the values received my master node has the following logic

val_slave_1 = getSlave1Val();
val_slave_2 = getSlave2Val();
val_slave_3 = getSlave3Val();

switch(val_slave_1):
  case A:
    switch(val_slave_2):
      case 1:
        switch(val_slave_3):
          case a: {do Z}
          case b: {do Y}
          case c: {do X}
      case 2:
        switch(val_slave_3):
          case a: {do W}
          case b: {do V}
          case c: {do U}
  case B:
    switch(val_slave_2):
      case 1:
        switch(val_slave_3):
          case a: {do T}
          case b: {do S}
          case c: {do R}
      case 2:
        switch(val_slave_3):
          case a: {do Q}
          case b: {do P}
          case c: {do O}

The advantages of this code are -

  1. Given 3 numbers I can find out exactly what behavior to expect.
  2. It's easy to debug.
  3. Do not have to maintain several booleans or if statements.

The problem with this code is that it -

  1. The current code is a combination of (2,2,3) cases permuted together, but in reality I have a lot more enums (3, 4, 7). This makes it extremely hard to read and maintain.
  2. If in the future one of the slaves changes the number of enums, say slave 2 adds another enum. I will need to add a whole lot of cases to make this work making it really hard to test
  3. If there is another independent slave (slave 4) which comes along and provides information, I'm pretty screwed.

My question to you all is that is there a better way to do this? I read a lot of places which said polymorphism is often a good way to solve switch statements but I tried making my code polymorphic and can't seem to nail down a solution. While a lot of people gave simple examples of vehicles and cats, it doesn't seem like I can apply it to my problem.

Another important note: it may be obvious but I'll still write it. It doesnt matter what order I write switch case statements in. To maintain sanity I will choose to write it in the order of most to least enums to save lines of code (I think)

The closest thread I found to this problem is -

Is there any design pattern to avoid a nested switch case?

But is there a better way to do this than maintaining a dictionary of enums to map to a function?

XorThaX
  • 1
  • 1
  • What do you dislike about using a map of functions? – Peter Ruderman Nov 19 '18 at 19:57
  • 1
    Is there any commonality between the leaf-cases? Do you want your code to break if a new enum option appears, or do you want some kind of fallback? Roughly how "hot" is the code path, on a scale of 0 is "runs once when a user clicks a mouse button" (~1 Hz) to 9 is "being called per-pixel on a 60 Hz 5k display" (~1 GHz)? – Yakk - Adam Nevraumont Nov 19 '18 at 19:59
  • `std::map`/`std::unordred_map` looks like what you want. Make a struct that contains each enum as a member and then map the values to the function you want to run. – NathanOliver Nov 19 '18 at 20:08
  • @peterruderman is right. You should try using map of functions – Leela Venkatesh K Nov 19 '18 at 20:43
  • @PeterRuderman I guess I was looking for ways to do it with polymorphism or other concepts than a map. i don't dislike it, i was looking for cleaner alternatives but i guess my problem doesnt have a "clean solution" – XorThaX Nov 20 '18 at 16:41
  • @Yakk-AdamNevraumont . the code runs at 50Hz – XorThaX Nov 20 '18 at 16:41
  • @XorThaX So I asked 3 questions. And you answered one. Is that because you don't know the answers to the others? – Yakk - Adam Nevraumont Nov 20 '18 at 16:46
  • @Yakk-AdamNevraumont sorry! few of the leaf cases have commonality. they either perform different actions or do nothing or throw an exception. regarding code to break if a new enum appears - well yes and no. I havent thought about it that much. breaking would mean that the developer is forced write new conditions and that is good. Alternatively it's ok if there's a "unknown case" where an exception is thrown or something or some safe action is performed – XorThaX Nov 26 '18 at 16:24

1 Answers1

1

Because your enum values are small, a map from enum combinations to functions could just be an array. When you have 3, 4, and 7 values (i.e. 2, 2, and 3 bits, respectively), you could use a single byte for indexing into an array of function pointers. Something like this:

using Handler = void (*)();
std::array<Handler, 128> handlers = { doA, doB, doB, doG, nullptr, ..., doFoo };

int v1 = slave1(); // 0-6
int v2 = slave2(); // 0-3
int v3 = slave3(); // 0-2

int index = (v2 << 5) | (v1 * 3 + v3);
handlers[index]();

If your enum values are not continuous from 0 to n, you might have to remap them to that.

You might want to come up with a clever way of populating the array, since manually recalculating the indexes when something changes might take a while. One way I can think of right now is a constexpr function that takes a number of structs that each contain the enum values and a function pointer, populating an array from those. The structs would calculate the index, the function would just assign function pointers from indexes. Or something like that.

Pezo
  • 1,458
  • 9
  • 15
  • This answer strikes me as brittle and error prone as written. – Peter Ruderman Nov 19 '18 at 20:30
  • If you manually populate the array like I did in the example snippet, then yes I agree. Which is why I suggested something more expressive like using a function that populates the array from descriptive entries in arbitrary order. – Pezo Nov 19 '18 at 20:37
  • aren't unordered maps O(1) look up. Is there a reason why arrays would be better than maps? – XorThaX Nov 20 '18 at 16:44
  • `unordered_map` is still a node based data structure, so reading out a value has an extra lookup compared to an array (in the best case). If performance is critical, an array is a better option. – Peter Ruderman Nov 20 '18 at 16:50