Reading posts on Habré, came across such
question . The comments suggested solutions, but none of them suited the author in view of the overhead of function calls. And then I thought about it, but why not really use an ordinary
switch , counting from two parameters one hash, which is what to use in a
switch . But looking at the example of the author of the question more attentively, I realized that this option would not work just like that, since it is necessary to catch the
default nested
switches .
Suppose there are two variables
n and
m , each can take a value from 0 to 9, and we have the following structure:
switch (n) { case 0: { switch (m) { case 2: case 4: ... break; case 5: ... break; default: ... break; } } break; ... }
Since the value of each variable is placed in a byte, then let the hash function be as follows ((n <<
SHIFT ) + m) (where
SHIFT = 8).
And this is where the problem arises: if both values are determined, then we will get a specific number after calculating the hash. But if we say n = 0, and m is not equal to 2.4.5, what should be done in this case? To list the remaining options for performing default actions is too expensive, because their ranges of values can be much more than 0..9. That is, in fact, it is necessary to catch the value of the calculated hash falling into a certain range.
And then I thought, what if we list the
default ones themselves. That is, first write
case 's for all pairs
m and
n , for which you need to perform some actions, and then in
default declare a nested
switch with one parameter
n , this will enumerate all other combinations that need to be processed.
So what I got
Such a simple hash function could be implemented using a regular macro, but since constexpr functions appeared in C ++ 11, I decided to use them:
constexpr int hash(int n, int m) { return verifyValues(n, m) ? ((n << SHIFT) + m): -1; }
, verifyValues is a function to verify that the parameters lie in a given range. For this, the constants MAX_N, MAX_M are used, and if the parameters are not valid, -1 will be returned.
')
#define isInBound(min, value, max) ((value >= min) && (value <= max)) constexpr bool verifyValues(int n, int m) { return isInBound(0, n, MAX_N) && isInBound(0, m, MAX_M); }
Looking at the hash function it would also be nice to add the following check:
MAX_M < pow(2, SHIFT)
Additional code and checks hide using macros:
#define SWITCH(n, m) static_assert(MAX_M < pow(2, SHIFT), "shift value is not enough to cover all M values")
Here, when defining a
switch , a will immediately check the performance of our hash function, and later for each
case , it will be checked that its variables are in the specified ranges.
So with the first part figured out, now the actual section is
default .
Here it would be possible to simply register the nested
switch , but if he started writing macros, I would define it for him:
#define DEFAULT(n) \ case -1: ASSERT(false); break; \ default: switch(n) { #define DEFAULT_CASE(n) case n #define END_DEFAULT }
Since, when calculating the hash, the function can return -1 (in the case of passing incorrect parameters to it), a handler was added for
case -1 (in my example, this is an ordinary ASSERT).
As a result, I got this
switch with two parameters:
SWITCH(a, b) { CASE(0, 1): CASE(0, 2): CASE(0, 3): CASE(0, 4): ... break; CASE(5, 3): ... break; DEFAULT(a) DEFAULT_CASE(0): ... break; DEFAULT_CASE(1): ... break; DEFAULT_CASE(2): ... break; DEFAULT_CASE(3): ... break; DEFAULT_CASE(4): ... break; DEFAULT_CASE(5): ... break; DEFAULT_CASE(6): ... break; DEFAULT_CASE(7): ... break; DEFAULT_CASE(8): ... break; DEFAULT_CASE(9): ... break; END_DEFAULT }
The main thing here is not to forget that after the macro
DEFAULT you do not need to put a colon.
All code in its entirety const int MAX_N = 10; const int MAX_M = 10; const int SHIFT = 8; #define isInBound(min, value, max) ((value >= min) && (value <= max)) constexpr bool verifyValues(int n, int m) { return isInBound(0, n, MAX_N) && isInBound(0, m, MAX_M); } constexpr int hash(int n, int m) { return verifyValues(n, m) ? ((n << SHIFT) + m): -1; } #define SWITCH(n, m) static_assert(MAX_M < pow(2, SHIFT), "shift value is not enough to cover all M values"); \ switch(hash(n, m)) #define CASE(n, m) static_assert(verifyValues(n, m), "N or M value is out of range"); \ case hash(n, m) #define DEFAULT(n) \ case -1: Q_ASSERT(false); break; \ default: switch(n) { #define DEFAULT_CASE(n) case n #define END_DEFAULT } ... SWITCH(a, b) { CASE(0, 1): CASE(0, 2): CASE(0, 3): CASE(0, 4): printf("0, 1-4\n"); break; CASE(5, 3): printf("5, 3\n"); break; DEFAULT(a) DEFAULT_CASE(0): printf("0\n"); break; DEFAULT_CASE(1): printf("1\n"); break; DEFAULT_CASE(2): printf("2\n"); break; DEFAULT_CASE(3): printf("3\n"); break; DEFAULT_CASE(4): printf("4\n"); break; DEFAULT_CASE(5): printf("5\n"); break; DEFAULT_CASE(6): printf("6\n"); break; DEFAULT_CASE(7): printf("7\n"); break; DEFAULT_CASE(8): printf("8\n"); break; DEFAULT_CASE(9): printf("9\n"); break; END_DEFAULT }
As a result, we have a
switch that works with two parameters. From overhead elementary hash function. But at the same time for the
case 's, for which both parameters are defined, the transition will be made already in the first
switch ' e, and for
default as in the second.
I hope that this implementation will be useful to somebody. Thanks for attention.
PS In principle, using such a structure, you can write a
switch not only for two parameters, but also for three or more.