Reputation: 57
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
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
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
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
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
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
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
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