3

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?

Filburt
  • 17,626
  • 12
  • 64
  • 115
user2063770
  • 57
  • 1
  • 7
  • 3
    this question may be helpful. http://stackoverflow.com/questions/126409/ways-to-eliminate-switch-in-code – Rohit Feb 12 '13 at 07:59
  • 1
    +1 for polymorphism in that linked answer. – Najzero Feb 12 '13 at 08:04
  • These "usecases", are they a kind of a state? So your class is a state machine which changes its state (`input1`) during execution? – leemes Feb 12 '13 at 08:15
  • @leemes:No, i dont have to change the state of my class based on the usecases, For simplicity , I could say we can represent the usecases through some const strings. – user2063770 Feb 12 '13 at 08:25
  • Ok what I meant was: Is the behavior of different input2 values within one use case (value1) more tightly bound together? Like: Is it a good idea to implement functions per use case to handle the value of `input2`? And more specific: Can they overlap? (For a particular value of input2, are there equal implementations among different values of input1?) – leemes Feb 12 '13 at 08:28
  • You are allowed to call functions in a switch statement. – Pete Becker Feb 12 '13 at 13:28

7 Answers7

10

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.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • What's wrong with `std::pair` as key? (Your macro is useful if you want to switch on the key, but it isn't necessary for a map.) – James Kanze Feb 12 '13 at 08:47
  • 1
    Also: I can't think of a case where `'A', 'B'` would have the same behavior as `'B', 'A'`, but who knows. If that were the case, wouldn't the simplest solution be to just put both entries into the map? – James Kanze Feb 12 '13 at 08:49
  • 1
    Finally, rather than use a function pointer, you could use a pointer to an abstract base class, with a derived class per action, and a static instance per derived class. The constructor of the abstraact base class could do the insertion into the map (provided arrangements are made to ensure correct order of construction). – James Kanze Feb 12 '13 at 08:51
  • @JamesKanze: Adding two entries for `(A,B)` and `(B,A)` could be problems : 1) it doubles the size of the map, 2) change could cause synchronization problem, i.e if you change the function-pointer for `(A,B)` and forgot to change the same for `(B,A)`, then you have two different behavior for `(A,B)` and `(B,A)`, they're not same anymore, and the problem will be harder to find. – Nawaz Feb 12 '13 at 09:01
  • @JamesKanze: I agree with the alternatives you suggested. The actual and simple solution depends on the exact problem which I'm unaware of. I just gave one possible solution, which avoids the `switch` altogether. – Nawaz Feb 12 '13 at 09:05
  • @Nawaz I understand that, and it's a good basis for an alternative solution. I've had to handle this problem fairly often, however, and thought I'd add some of the variants I've found useful. (Populating the map dynamically in the constructor of static functional objects has been particularly useful; the commands which are available depend on which DLLs are loaded, and you can give different clients different sets of commands by delivering different sets of DLLs. But of course, the OP may not need this kind of flexibility.) – James Kanze Feb 12 '13 at 09:24
  • @Nawaz But I do think that if you're using `std::map`, it's cleaner to use `std::pair` as a key, rather than `int`. – James Kanze Feb 12 '13 at 09:25
2

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;
}
billz
  • 44,644
  • 9
  • 83
  • 100
  • As I understand the question, there will be a function `processInput2` for each possible `input1`. So there will be, for example, `processInput2ForA`, `...ForB` etc. – leemes Feb 12 '13 at 08:12
  • it depends... but the question is vague. That's the simple solution I can provide – billz Feb 12 '13 at 08:18
1

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]();
leemes
  • 44,967
  • 21
  • 135
  • 183
0

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).

James Kanze
  • 150,581
  • 18
  • 184
  • 329
0

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;
}

...
Community
  • 1
  • 1
Dmitry Gordon
  • 2,229
  • 12
  • 20
-1

Why not use if branchers?

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

this is kind of writing what you mean.

wirrbel
  • 3,173
  • 3
  • 26
  • 49
-1

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)>());
}
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
user1095108
  • 14,119
  • 9
  • 58
  • 116