⬆️ ⬇️

Switch for two parameters in C ++

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"); \ 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) 


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.

Source: https://habr.com/ru/post/183332/



All Articles