September 16, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

To Switch or Not to Switch

  • July 29, 2005
  • By Radu Braniste
  • Send Email »
  • More Articles »

Crazy Little Thing Called Switch — An Overview

The "switch" is one of the most underrated statements in C++ programming. Usually, you'll find it mentioned in the "do not" sections of books and articles—associated with the dangers of manual type dispatching and implicitly denoting bad programming style. Not even efficiency saves it from obscurity. When code is optimized for speed or the case labels have a compact distribution of values, "switch" is implemented as a jump table—extremely fast without any manual intervention. The only possible optimization is to concentrate the most used cases on the first places inside the switch—on small embedded platforms, it might make a difference.

All the above means that there is not much to say about a "switch" and the subject is hopelessly uninteresting.

But wait—there might be something left out. Many times, "switch" is used in the context of "factories" or finite state machines as a fast dispatching mechanism based on integer type parameters. These (especially factories) are cases when there is no way to use a polymorphic approach as there are no types defined yet—a switch or a switch-like construct are the only possibilities.

The potential problem in these cases is how to maintain and extend a switch1. There is an idiom for extending a switch (see canon_switch.cpp):

struct A
{
   static int doSwitch(int pos)
   {
      switch (pos)
      {
         case 0: return 0;
         default: return -1;
      }
   }
};
template <class S>
struct B
{
   static int doSwitch(int pos)
   {
      switch (pos)
      {
         case 1: return 1;
         default: return S::doSwitch(pos);
      }
   }
};

Variations are possible: inheritance instead of composition, for example. The issue here is that individual cases are hard to maintain.

Another way is to use std::map instead of switch. This technique has the added benefit of dispatching on types other than integer and is easy to extend (just append a new instance). The penalty to pay is an additional step of loading the map. This is great as long as the map stays in memory and is used over and over again. But, in stateless designs, this might be an overburden.

There is a meta-programming version of "switch" too [2]:

template<int I>
class CASE {
public:
   static inline void f()    //default
   { }
};
class CASE<value1> {
public:
   static inline void f()
   { }
};
class CASE<value2> {
public:
   static inline void f()
   {}
};
CASE<I>::f();

It looks almost perfect, but (nothing is perfect!) it can be used at compile-time only.

I will try to investigate other options, balancing different factors and alternatives.

Templates Come to the Rescue

This is the first attempt:

template < int I>
struct X
{
   static int getValue(int pos)
   {
      if (pos== I)
         return I;
      return X<I -1>::getValue(pos);
   }
};
template < >
struct X<-1>
{
   static int getValue(int pos)
   {
      return -1;
   }
};
template < >
struct X<0>
{
   static int getValue(int pos)
   {
      if (pos == 0)
         return 0;
      return X<-1>::getValue(pos);
   }
};
void doSwitch(int pos)
{
   std::cout << X<I>::getValue(pos);
}

This design exposes the meta-programming model to the real-time realm through chaining. Is it very efficient (the chain is actually calculated at compile time), but it doesn't do a very good job in hiding the chaining mechanism itself—every specialization has to (re)implement it (see X<0>).

Using a Curiously Recurring Template Pattern

An alternative is to use CRTP (Curiously Recurring Template Pattern)3—and factor out the mechanism (see sequence.cpp):

template <template <int> class T, int I>
struct Root
{
   int run(int pos) const
   {
      const T<I>& ref = static_cast<const T<I>&>(*this);
      if (pos== I)
         return ref.getValue();
      return T<I-1>().run(pos); 
   }
};
template <template <int> class T>
struct Root<T, -1>
{
   int run(int pos) const
   {
      return -1; 
   }
};
template < int I>
struct X : Root<X, I>
{
   static  int getValue()
   {
      return I;
   }
};
template < >
struct X<2> : Root<X, 2>
{
   static  int getValue()
   {
      return 10;
   }
};
void doSeq(int j)
{
   std::cout << X<10>().run(j) << std::endl;
}

Now, Root contains the advancing mechanism—the specializations only contain what's traditionally found in case statements. Unfortunately, the "template template" non-type specialization used in the above code is not fully supported by some compilers at this moment (VC 7.1, for example).





Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel