user2063770
user2063770

Reputation: 57

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

I have seen similar kind of threads, But, not sure how to exactly apply the solutions to my case. My problem is that i have a set of usecases lets say 'A','B','C',There are certain commands i need to execute when the input passed(2 usecases are the input) is any 2 of the listed usecases. for example:

switch(input1)
{
case A:
break;
case B:
break;
case C:
break;
}

inside the each case, i will have to check on input 2, so, the final code could look like

switch(input1)
{
case A:
{
switch(input2):
case B:
break;
case c:
break;
}
case B:
{
switch(input2):
case A:
break;
case c:
break;
}
....

}

I was thinking to use a map of (pair,command) and remove this switch cases, but is there any alternative better solution or design problem to solve this problem?

Upvotes: 3

Views: 9750

Answers (7)

user1095108
user1095108

Reputation: 14603

Instead of having an array or a map, the argument list might do (toss out the return value, if you don't want it)?

constexpr auto dispatch(auto const i, auto&& ...f)
  noexcept(noexcept((f(), ...)))
  requires(std::is_enum_v<std::remove_const_t<decltype(i)>>)
{
  using int_t = std::underlying_type_t<std::remove_cvref_t<decltype(i)>>;

  using tuple_t = std::tuple<decltype(f)...>;
  using R = decltype(std::declval<std::tuple_element_t<0, tuple_t>>()());

  return [&]<auto ...I>(std::integer_sequence<int_t, I...>)
    noexcept(noexcept((f(), ...)))
  {
    if constexpr(std::is_void_v<R>)
    {
      ((I == int_t(i) ? (f(), 0) : 0), ...);
    }
    else
    {
      R r{};

      ((I == int_t(i) ? (r = f(), 0) : 0), ...);

      return r;
    }
  }(std::make_integer_sequence<int_t, sizeof...(f)>());
}

Upvotes: -1

Dmitry Gordon
Dmitry Gordon

Reputation: 2324

The proposed answers with map or table of pointers to handle functions are ok. But I see two downsides: 1) A small performance decrease comparing to the manual nested switches. 2) Case handling methods aren't fully self-descriptive. I mean you have to mention each handle methods twice - in its' definition and in the place where you init the map.

I see two alternative options: 1) Source code generation. Automatically generate nested switches from some kind of representation. Well... it's very good option to create optimal code, if don't mind adding code generation for such a small task. 2) Using preprocessor hacks. Not the most elegant but quite interest way to make it work.

First we declare X-Macro for our enum:

#define ELEMENTS(processor) \
processor(firstElement)     \
processor(secondElement)    \
processor(thirdElement)

We can use it to declare the enum itself:

#define ENUM_PROCESSOR(arg) arg,

enum class
{
    ELEMENTS(ENUM_PROCESSOR)
};

#undef ENUM_PROCESSOR
Now we can add method that uses macros to generate nested switches:

void processElements(const Elements element1,
                     const Elements element2)
{
    // These macros are useful to trick the preprocessor to allow recursive macro calls
    // https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms
    #define EMPTY(...)
    #define DEFER(...) __VA_ARGS__ EMPTY()
    #define EXPAND(...) __VA_ARGS__
    #define ELEMENTS_INT() ELEMENTS

    #define PROCESS_NESTED_ENUM_VALUE(value)                                         \
    case Elements::value:                                                            \
    {                                                                                \
        process<Value1, Elements::value>();                                          \
        break;                                                                       \
    }

    #define PROCESS_ENUM_VALUE(value)                                                \
    case Elements::value:                                                            \
    {                                                                                \
        constexpr Elements Value1 = Elements::value;                                 \
        switch (element2)                                                            \
        {                                                                            \
            DEFER(ELEMENTS_INT)()(PROCESS_NESTED_ENUM_VALUE)                         \
        };                                                                           \
                                                                                     \
        break;                                                                       \
    }

    switch (element1)
    {
        EXPAND(ELEMENTS(PROCESS_ENUM_VALUE));
    };

    #undef EMPTY
    #undef DEFER
    #undef EXPAND

    #undef ELEMENT_TYPES_INT
    #undef PROCESS_ENUM_VALUE
    #undef PROCESS_NESTED_ENUM_VALUE
}

A lot of efforts here are made to "trick" preprocessor to expand ELEMENTS recursively. The main idea is well described here.

Now we declare our handlers as template function specialization:

template <Elements Element1, Elements Element2>
void process();

template<>
void process<Elements::firstElement, Elements::firstElement>()
{
    //some code 1;
}

...

Upvotes: 0

James Kanze
James Kanze

Reputation: 153909

You could always do something like:

switch ( 256 * input1 + input2 ) {
case 256 * 'A' + 'B':
    //  ...
    break;
//  ...
};

But frankly, in this case, I'd find the nested switches easier to understand, supposing that switch is the right answer to your problem. For character input, it often is, but there are other alternatives, such as a std::map<std::pair<char, char>, Action const*>, where Action is a virtual base class, and each action in the map is a static instance of a derived class. This has the advantage of making each action a distinct object (which may not be an advantage, depending on what you do in the actions), and if the map is populated dynamically (for example in the constructor of Action), you can add actions without modifying the source code of the parser (but you may not need this flexibility).

Upvotes: 0

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361352

If performance is not that a big issue, then a map of function pointers could be one solution.

Assuming the label A, B , C ... are small integral values less than 255.

  • Setup the map first

    #define KEY(a,b)  ( (a<<8) | b )
    
    std::map<int, function_pointer_type>  dispatcher =
    {
        { KEY(A,B), ab_handler},
        { KEY(A,C), ac_handler},
        { KEY(B,C), bc_handler},
        //etc
    };
    
  • Use the map to invoke appropriate handler for each set of input:

     dispatcher[KEY(input1,input2)] (/* args */);
    

Note that you have to setup the dispatcher with each possible pair of inputs. Also, if the pair KEY(A,B) and KEY(B,A) are same case, then you can write a function called invoke to handle this case in order to provide uniform usage for the rest of the code.

 void invoke(int input1, int input2, /* args */)
 {
     if (dispatcher.find(KEY(input1, input2)) != dispatcher.end() )
           dispatcher[KEY(input1,input2)] (/* args */);
     else
           dispatcher[KEY(input2,input1)] (/* args */);
 }

then use it as:

 invoke(input1, input2, /* args */);
 invoke(input2, input1, /* args */);  //still okay!

Hope that helps.

Upvotes: 10

leemes
leemes

Reputation: 45665

One possibility is to split the code into one function per nested case, so your example would have 6 functions:

void process_A_A() { ... }
void process_A_B() { ... }
void process_B_A() { ... }
void process_B_B() { ... }
void process_C_A() { ... }
void process_C_B() { ... }

Then, put them into an array in initialization for very fast (constant-time) lookup during runtime:

typedef std::function<void(void)> Function;  // or: void (*)(void)
Function f[Input1Count][Input2Count];
f[A][A] = &process_A_A;
f[A][B] = &process_A_B;
...

To call the appropriate function, write:

f[input1][input2]();

Note that by using the C++11 std::function type, the functions don't have to be classical function pointers; they can also be lambda functions or functor objects.

You can also keep some parts empty or assign the same function multiple times. When you decide to keep some entries empty (so there shouldn't be anything done in such a case), check the function object before calling it:

if (f[input1][input2])
    f[input1][input2]();

Upvotes: 1

wirrbel
wirrbel

Reputation: 3299

Why not use if branchers?

if (input1 == A && input2 == B) {
} else if (input1==A && input2 = C) {
} ...

this is kind of writing what you mean.

Upvotes: -1

billz
billz

Reputation: 45410

In your case, how about break the two switches into two functions

bool processInput2(char input2)
{
  switch(input2)
  {
   case 'A':
   {  
      // blah
   }
    break;
}

bool processInput1(char input1)
{
  switch(input1)
  {
   case 'A':
      processInput2(input2);
      break;
}

Upvotes: 2

Related Questions