HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

C++ compile-time checked switch 'statement'

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
switchcompilestatementtimechecked

Problem

In the project I work on there are several places where a switch statement is used on a type enum. (I know, better to use virtual functions or a visitor pattern or something, but sometimes switching on type codes is unavoidable - e.g., deserializing XML.) All of our type enums are of the simple form enum { TypeA, TypeB, TypeC, NumTypes } so it is known at compile time that all type codes for a given type are in the half-open range [0, NumTypes).

I decided to see if I could make the compiler verify that all type codes were being checked, and this is what I came up with:

// Switch.h

template 
struct Switcher
{
    typename Dispatcher::ResultType
    operator()( const Dispatcher& dispatcher, int iCase ) const
    {
        if ( iCase == iIndex )
        {
            return dispatcher.template Case();
        }
        else
        {
            return Switcher()( dispatcher, iCase );
        }
    }
};

template 
struct Switcher
{
    typename Dispatcher::ResultType
    operator()( const Dispatcher& dispatcher, int iCase ) const
    {
        return dispatcher.Default( iCase );
    }
};

template 
typename Dispatcher::ResultType
Switch( const Dispatcher& dispatcher, int iCase )
{
    return Switcher()( dispatcher, iCase );
}


Example use:

#include "Switch.h"

#include 
#include 
#include 

enum MyType { TypeA, TypeB, TypeC, NumTypes };

class MyDispatcher
{
public:
    typedef std::string ResultType;

    template 
    std::string Case() const;

    std::string Default( int iType ) const;
};

template <>
std::string
MyDispatcher::Case() const
{
    return "TypeA";
};

template <>
std::string
MyDispatcher::Case() const
{
    return "TypeB";
};

template <>
std::string
MyDispatcher::Case() const
{
    return "TypeC";
};

std::string
MyDispatcher::Default( int iType ) const 
{
    std::cout( MyDispatcher(), iType );

    std::cout << "Type name: " << zTypeName << std::endl;
}


The Switcher template class will recursively instantiate versions of it

Solution

That seems like an awful lot of boilerplate code to replace a single diagnostic that already exists in a lot of compilers. You're replacing

switch (fruit) {
  case APPLE: return "apple"; break;
  case PEAR: return "pear"; break;
  case PLUM: return "plum"; break;
}


with


NEWSWITCH(uniqueID, std::string);
DEFCASE(uniqueID, APPLE) { return "apple"; }
DEFCASE(uniqueID, PEAR) { return "pear"; }
DEFCASE(uniqueID, PLUM) { return "plum"; }
DEFDEFAULT(uniqueID) { assert(false); }

...
Switch(uniqueID(), fruit);


The most onerous requirement there is putting all the switch logic at file scope; you can't localize it to the actual codepath where it's going to be used. The second most onerous requirement is coming up with uniqueID, a global identifier which must be different for each switch that you convert using this pattern.

A much better solution to your original problem would be to use an array:

int main() {
    MyType iType = TypeB;

    std::string typenames[NumTypes] = { "TypeA", "TypeB", "TypeC" };

    assert(0 <= (int)iType && (int)iType < NumTypes);
    std::string zTypeName = typenames[iType];

    std::cout << "Type name: " << zTypeName << std::endl;
}


This code statically enforces that each index in 0..NumTypes-1 is associated with a std::string in the array; and (just like your original code) it dynamically asserts that iType is in the proper range.

Code Snippets

switch (fruit) {
  case APPLE: return "apple"; break;
  case PEAR: return "pear"; break;
  case PLUM: return "plum"; break;
}
<at file scope>
NEWSWITCH(uniqueID, std::string);
DEFCASE(uniqueID, APPLE) { return "apple"; }
DEFCASE(uniqueID, PEAR) { return "pear"; }
DEFCASE(uniqueID, PLUM) { return "plum"; }
DEFDEFAULT(uniqueID) { assert(false); }

...
Switch<NumTypes>(uniqueID(), fruit);
int main() {
    MyType iType = TypeB;

    std::string typenames[NumTypes] = { "TypeA", "TypeB", "TypeC" };

    assert(0 <= (int)iType && (int)iType < NumTypes);
    std::string zTypeName = typenames[iType];

    std::cout << "Type name: " << zTypeName << std::endl;
}

Context

StackExchange Code Review Q#24048, answer score: 5

Revisions (0)

No revisions yet.