patterncppMinor
C++ compile-time checked switch 'statement'
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
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:
Example use:
The
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 itSolution
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
with
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
A much better solution to your original problem would be to use an array:
This code statically enforces that each index in
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.