Jonathan Boccara's blog

How to Flatten Out A Nested Switch Statement

Published June 27, 2017 - 2 Comments

With my team we’ve recently come across an annoying switch nested in another switch statement, and I want to show a solution for flattening out this sort of structure.

Flatten switch case nested collapse

Motivation

Let’s consider two enums representing the size and color of a shirt. While I don’t work in the clothing industry, using a simple example by stripping away all the domain specifics allows to focus on the C++ technique here.

The shirts come in two colors and three sizes:

enum class Color
{
    Red,
    Blue
};
enum class Size
{
    Small,
    Medium,
    Large
};

We do a specific treatment for each of the combination of color and size of a shirt. Expressing this with switches gives something like this:

switch (color)
{
    case Color::Red:
    {
        switch (size)
        {
            case Size::Small:
            {
                // code for color red and size Small
                break;
            }
            case Size::Medium:
            {
                // code for color red and size Medium
                break;
            }
            case Size::Large:
            {
                // code for color red and size Large
                break;
            }
            default:
            {
                throw WrongEnumValues();
            }
        }
    }
    case Color::Blue:
    {
        switch (size)
        {
            case Size::Small:
            {
                // code for color blue and size Small
                break;
            }
            case Size::Medium:
            {
                // code for color blue and size Medium
                break;
            }
            case Size::Large:
            {
                // code for color blue and size Large
                break;
            }
            default:
            {
                throw WrongEnumValues();
            }
        }
    }
}

Several things are damaging the expressiveness of this piece of code:

  • it’s lengthy but without containing a lot of information,
  • the associated colors and sizes are far apart from one another: for example the case Size::Large within the case Color::Red is closer to the case Color::Blue in terms of lines of code than from the case Color::Red to which it belongs.
  • this design doesn’t scale: imagine that a third enum was involved. The code would then become even harder to read.

To make this code more expressive, I’m going to show how to flatten the double switch into a single one.

Collapsing a switch

Here’s an easy way to do this: creating an new enum that represents all the combinations of the other enums, and use it in the switch statement.

Let’s do it manually once, and then write a generic code to do it for us.

Here is the enum representing the combinations:

enum class Color_Size
{
    Blue_Small,
    Blue_Medium,
    Blue_Large,
    Red_Small,
    Red_Medium,
    Red_Large
};

The ugly double switch can be encapsulated into a function that does the mapping between the original enum and this new one:

constexpr Color_Size combineEnums(Color color, Size size)
{
    switch (color)
    {
        case Color::Red:
        {
            switch (size)
            {
                case Size::Small: return Color_Size::Blue_Small;
                case Size::Medium: return Color_Size::Blue_Medium;
                case Size::Large: return Color_Size::Blue_Large;
                default: throw WrongEnumValues();
            }
        }
        case Color::Blue:
        {
            switch (size)
            {
                case Size::Small: return Color_Size::Red_Small;
                case Size::Medium: return Color_Size::Red_Medium;
                case Size::Large: return Color_Size::Red_Large;
                default: throw WrongEnumValues();
            }
        }
    }
}

And then we can do a single switch statement on the combination of values. The key for this to work is that the combineEnums function is constexpr, so its return value can be put into a switch statement:

switch (combineEnums(color, size))
{
    case combineEnums(Color::Red, Size::Small):
    {
        // code for color red and size Small
        break;
    }
    case combineEnums(Color::Red, Size::Medium):
    {
        // code for color red and size Medium
        break;
    }
    case combineEnums(Color::Red, Size::Large):
    {
        // code for color red and size Large
        break;
    }
    case combineEnums(Color::Blue, Size::Small):
    {
        // code for color blue and size Small
        break;
    }
    case combineEnums(Color::Blue, Size::Medium):
    {
        // code for color blue and size Medium
        break;
    }
    case combineEnums(Color::Blue, Size::Large):
    {
        // code for color blue and size Large
        break;
    }
    default:
    {
        throw WrongEnumValues();
    }
}

You’ll note that a constexpr function can throw exceptions. While this seems strange at first, it’s logical because a constexpr function can also be called at runtime. And if it ever tries to throw at compile time, the program doesn’t compile. All this is very well explained in Dietmar Kühl’s Constant Fun talk at CppCon on constexpr.

Although the switch statement has been flattened out, there is a lot of code that could be automated here.

Combining the enums automatically

Prerequisite: The generic solution I propose is based on one prerequisite: that the enums have all an extra last element with a consistent name, say “End_”, and that its value is not customized (as in End_ = 42). We could choose any other name, but I like “End_” because it has the same semantics of “one after the last one” as in the STL. I need this to manipulate the enums together (if you can think of a way to fill the same need without the End_, the comments section is all yours).

So our two enums become:

enum class Color
{
    Red,
    Blue,
    End_
};

enum class Size
{
    Small,
    Medium,
    Large,
    End_
};

The idea is now to give a unique value for each association of enum values. The most compact (and, in my opinion, the most natural) way to do this is by using the following formula:

combinedValue = (Color value) + (numbers of possible color values) * (Size value)

One way to view this formula is that for each value of the Size enum, there are as many values as there are possible Colors.

The formula manipulates enum values like numeric values. To do this, we throw away all the type safety brought by the enum classes:

template<typename Enum>
constexpr size_t enumValue(Enum e)
{
    return static_cast<size_t>(e);
}

This code snippet is supposed to make you feel very uneasy. But worry not, we’ll put all the type safety back in just a moment.

And here is how to get the number of possible values of an enum:

template<typename Enum>
constexpr size_t enumSize()
{
    return enumValue(Enum::End_);
}

Hence the need for End_.

And here is the implementation of the formula:

template<typename Enum1, typename Enum2>
constexpr size_t combineEnums(Enum1 e1, Enum2 e2)
{
    return enumValue(e1) + enumSize<Enum1>() * enumValue(e2);
}

which is still constexpr, to be able to fit into the cases of a switch statement.

Putting type safety back

Now have a look at this example of usage. See anything wrong?

switch (combineEnums(color, size))
{
    case combineEnums(Color::Red, Size::Small):
    {
        // code for color red and size Small
        break;
    }
    case combineEnums(Color::Red, Size::Medium):
    {
        // code for color red and size Medium
        break;
    }
    case combineEnums(Size::Small, Size::Large):
    {
        // code for color red and size Large
        break;
    }
    case combineEnums(Color::Blue, Size::Small):
    {
        // code for color blue and size Small
        break;
    }
    case combineEnums(Color::Blue, Size::Medium):
    {
        // code for color blue and size Medium
        break;
    }
    case combineEnums(Color::Blue, Size::Large):
    {
        // code for color blue and size Large
        break;
    }
    default:
    {
        throw WrongEnumValues();
    }
}

There is a bug in the third case:

case combineEnums(Size::Small, Size::Large):

This could happen because I’ve thrown away type safety a little earlier. I really asked for this one.

A way to put type safety back in place is to add typing to the combineEnums function. To do this I’m going to:

  • transform the combineEnums function into a function object
  • move the template types corresponding to the enums over to the object rather than the function
  • use the same object instance in the whole switch statement.

So for a start, here is the function’s code packed into an object:

template<typename Enum1, typename Enum2>
struct CombineEnums
{
    constexpr size_t operator()(Enum1 e1, Enum2 e2)
    {
        return enumValue(e1) * enumSize<Enum2>() + enumValue(e2);
    }
};

Then we construct the object with the right enum types before the switch statement:

CombineEnums<Color, Size> combineEnums;
switch (combineEnums(color, size))
{
    case combineEnums(Color::Red, Size::Small):
    {
        ....

and using the wrong enum in a case becomes a compilation error:

error: no match for call to '(CombineEnum<Color, Size>) (Size, Size)'

Safety’s back.

Going generic

EDIT: I thought that a simple recursion on variadic templates was enough to make this technique work on any number of enums. But as reddit user /u/minirop pointed out with a revealing example, I was wrong. The presented implementation only works for two enums. Therefore I’ll leave this section empty and will re-work the implementation to make it more generic. This will be the topic of a later post.

Stepping back

I’ve found this technique efficient to flatten out switch statements and to bring associated values together in the cases. This really improves code readability.

However, it may not be the right choice for every situation (what is, really). For instance this technique doesn’t let you have a case covering a given value of Color for all possible values of Size.

Also, switches on enums often raise the question of hidden polymorphism: wouldn’t these enums be better off refactored into types? In this case the need to route on several types draws the code into multiple dispatch, which C++ doesn’t support natively. One solution for this is the (much criticized) visitor pattern.

But enums are there for a reason. And when switches start nesting into one another, this technique for ironing them out comes in handy.

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed