Multi-Variable Switch Statements

Just as a thought exercise I was trying to convert a certain type of if statement that used multiple variables in the evaluating expression into switch statements, something like if( var1 == A && var2 == B ). The solution worked out pretty OK so I wanted to write something about it. Let's start with a quick review of if and switch statements:

If statements evaluate Boolean expressions whereas switch evaluate constant integers. Say you had a variable that contained some enumerated value. The if ladder to see what value it contained would be:

if( val == A ) ...;
else if( val == B ) ...;
else if( val == C ) ...;
else ...;

Each if statement containts an expression that compares "val" to a single constant. Let's look at the equivalent switch statement:

switch( val ) {
	case A: ...; break;
	case B: ...; break;
	case C: ...; break;
	default: ...; break;
}

Here we start with stating what value we want to test against, which is the one contained in the "val" variable. Each case label is followed by a constant integer and the code jumps to the matching value.

Switch statements are generally faster as there's just one evaluation in the switch() statement though a lot of simple if ladders like the one above can be optimized into a switch statement by the compiler.
If statements are however much more flexible since each statement can have its own evaluating expression that doesn't have to have any relation to the ones above or below it. Like this:

if( val == A ) ...;
else if( B == val ) ...;
else if( val == foo ) ...;
else if( bar = 1 ) ...;
else if( func() ) ...;

You can switch around the evaluation order like in the first two cases or you can test against a non-constant like in the third case, test a completely unrelated variable in the fourth or call a function in the fifth. This flexibility can make it more difficult to read if ladders and figure out just what is intended because what's being evaluated might change farther down the ladder. In switch statements you know by reading the first line what's being evaluated and that cannot change during the statement.
Due to their flexibility you tend to favor if statements and you often have to go out of your way to make something into a switch statement. My problem was that I had the following if ladder that used two variables:

if( val1 == A && val2 == B ) ...;
else if( val1 == C && val2 == A ) ...;
else if( val1 == F && val2 == B ) ...;

If you intended to evaluate every possible combination of val1 and val2 then using cascaded if statements would be easier.

if( val1 = A ) {
	if( val2 == A ) ...;
	else if( val2 == B ) ...;
	else if( val2 == C ) ...;
}
if( val1 = B ) {
	if( val2 == A ) ...;
	else if( val2 == B ) ...;
	else if( val2 == C ) ...;
}

These could easily be converted into cascaded switch statements:

switch( val1 ) {
	case A:
		switch( val2 ) {
			case A: ...; break;
			case B: ...; break;
			case C: ...; break;
		}
	case B:
		switch( val2 ) {
			case A: ...; break;
			case B: ...; break;
			case C: ...; break;
		}
}

This has the downside of having more indentation levels but it's still easier to read for reason of the clearer syntax of switch statements compared to if. However if you only need to test against certain combinations then cascading the statements would be unnecessary. Then the if statement is superior since it can be made flat like above where you do two evaluations in each if statement, testing both val1 and val2. With switch statements you'd always have to have two of them to test two variables and this is what I wanted to avoid doing.

Because switch statements can only perform one initial evaluation you'd need some way to combine val1 and val2 and evaluate both of them at the same time. First I thought of using a bit field where each possible value represented a bit position instead of an integer. So normally the elements of an enum would have sequential integer values:

enum alphabet { A = 0, B = 1, C = 2, D = 3 ... };

Instead A would be the first bit position, B the second and so on. That would make their integer values change like this instead:

enum alphabet { A = 1, B = 2, C = 4, D = 8 ... };

Then if you added val1 and val2 together and the result was 6 you'd know that one of them had to be 2 and the other 4. The problem being that you can't test for the order of the values since val1 = 2 and val2 = 4 yields the same result as val1 = 4 and val2 = 2. You need some non-commutative way of doing the calculation.

Lets create a combinatory table to visualize what we're trying to do:

A B C D
A AA AB AC AD
B BA BB BC BD
C CA CB CC CD
D DA DB DC DD

Each of these possible combinations should have a unique value that we can then test against after combining the values. The simplest thing would of course be to label them sequentially:

A B C D
A 0 1 2 3
B 4 5 6 7
C 8 9 10 11
D 12 13 14 15

This looks just like a two dimensional array and the numbers the array indices. We know how to use pointer arithmetic to access data in sequentially allocated 2D array using x and y coordinates:

*( ptr + x + y * width );

As an example if we have a 2D array with a width of 100 elements and we want to access the data at X = 15 and Y = 27 then we first calculate the index this element is at:

15 + 27 * 100 = 2715;

Then we add 2715 to the pointer's memory address. We can use the same arithmetic to assign a unique value to combinations of val1 and val2 if we treat them like the X and Y coordinates in a 2D array and from there have a single switch statement testing against this now single compound value:

enum alphabet { A = 0, B, C, D ..., NBR_LETTERS };

int compoundVal = val1 + val2 * NBR_LETTERS;
switch( compoundVal ) {
	case A + B * NBR_LETTERS: ...; break;
	case C + A * NBR_LETTERS: ...; break;
	case F + B * NBR_LETTERS: ...; break;
}

NBR_LETTERS is the total number of elements in the enum and therefore equivalent to both the width and height of the combinatory array above. Using pointer like arithmetic we can calculate a unique integer value for each switch case. Importantly it matters what position the variables are in when calculating "compoundVal" and so it differentiates in what order the values are presented.

Here's some example code comparing an if ladder to using multi-variable switch. In testing the switch case was faster except when using O2, then they became equal. For some reason when using O3 the if ladder was back to being slower than the switch by about 30%. Might be a bug in GCC?

Probably an answer to a question no one's asking but I thought it was an interesting experiment and I personally prefer switch statements so being able to use them more often is what I was after. You need to be dealing with variables with finite ranges so enums are the most obvious application but things like combinations of ASCII characters would work just as well as there's only 128 of those. Just like arrays this is not limited to two dimensions and will work the same with multi-dimensional pointer arithmetic.