September 1, 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 »

A Versioning Alternative to Inheritance

Of course, things can be further improved. Why switch to using integer parameters only? Why rigidly require all the classes to be part of the same family (X<I>)? Why not be able to extend the "case" classes? Analyze the requirements one by one.

A "generalized switch" can be created by isolating the comparisons and making the return mechanism more generic. This, together with a PREVIOUS_TYPE composite, solves the first two issues.

Extending a "case" is more complicated. What you want to achieve is a guaranteed usage of the latest extended version. For example, having a switch-like chain containing CASE1 and CASE2, where CASE2 has a newer implementation CASE2_1, you would like to chain CASE1 and CASE2_1 together at compile time. The compiler has to figure out that, at most, two versions of CASE2 exist and to pick up the latest.

Note that inheritance cannot be used because we don't know in advance the latest subclass. One solution might be the following:

template <class T>
struct Root
{
   enum  { VERSION  = 0};
   template <class U, class Z>
   void operator()( const U& i, const Z& z) const
   {
      typedef typename T::PREVIOUS_TYPE W;
      const T& ref = static_cast<const T&>(*this);
      ref.equals(i) ? ref.evaluate(z) : W()(i, z); 
   }
};
template <>
struct Root<void>
{
   template <class U, class Z>
   void operator()( const U&  i, const Z& z)
   { z("NOT_FOUND");}
};
typedef Root<void> ROOT_NOT_FOUND;
template <int I>
struct NOT_FOUND : Root<NOT_FOUND<I> >
{
   typedef  ROOT_NOT_FOUND PREVIOUS_TYPE;
   template <class U> bool equals(const U& i)  const
   {
      return false;
   }
   template <class Z>
      void evaluate(const Z& z) const
   {}
};
template <int I>
struct T0 : Root<T0<I> >
{
   typedef NOT_FOUND< NOT_FOUND<1>::VERSION> PREVIOUS_TYPE;
   template <class U> bool equals(const U& i)  const
   {
      return (i == 0);
   }
   template <class Z>
   void evaluate(const Z& z) const
   {
      z("T00");
   }
};
template <>
struct T0<1> : Root<T0<1> >
{
   typedef NOT_FOUND< NOT_FOUND<1>::VERSION> PREVIOUS_TYPE;
   enum  { VERSION  = T0<2>::VERSION == 0 ? 1 : T0<2>::VERSION};
   template <class U> bool equals(const U& i)  const
   {
      return (i == 0);
   }
   template <class Z>
   void evaluate(const Z& z) const
   {
      z("T01");
   }
};
template <int I>
struct T7 : Root<T7<I> >
{
   typedef T0< T0<1>::VERSION> PREVIOUS_TYPE;
   template <class U> bool equals(const U& i)  const
   {
      return (i == 60);
   }
   template <class Z>
   void evaluate(const Z& z) const
   {
      z(60);
   }
};
template <>
struct T7<1> : Root<T7<1> >
{
   typedef T0< T0<1>::VERSION> PREVIOUS_TYPE;
   enum  { VERSION  = T7<2>::VERSION == 0 ? 1 : T7<2>::VERSION};
   template <class U> bool equals(const U& i)  const
   {
      return (i == 60);
   }
   template <class Z>
   void evaluate(const Z& z) const
   {
      z(601);
   }
};
struct T9 : Root<T9>
{
   typedef T7< T7<1>::VERSION> PREVIOUS_TYPE;
   template <class U> bool equals(const U& i)  const
   {
      return (i == 120);
   }
   template <class Z>
   void evaluate(const Z& z) const
   {
      z("120");
   }
};
struct O
{
   template <class Z>
   void operator()(const Z& z) const
   {
      std::cout << "CRTP " << z << std::endl;
   }
};
void doCurRecur(int j)
{
   T9()(j, O());
}

Let's analyze the previous design a little bit.

We are still dealing with CRTP—only that the advancing mechanism is base now on PREVIOUS_TYPE (actually is a backwards going mechanism). And PREVIOUS_TYPE itself uses VERSION, which advances through all the versions of a certain "case" and collects the latest one—using constructs like:

typedef T0< T0<1>::VERSION> PREVIOUS_TYPE;
enum  { VERSION  = T7<2>::VERSION == 0 ? 1 : T7<2>::VERSION};

Of course, the latest design can be subject to other improvements (for example, policies can be used to encapsulate the versioning mechanism) but this is beyond the scope of this article, which just wanted to demonstrate that there are open options even when dealing with well-established programming concepts.

Download

Download the accompanying code's zip file here.

Conclusion

This article tried to analyze patterns associated to "switch" statement usage in C++ and generalize the concept of a "switch." Possible designs of a more generic "switch" were described together with a way of extending classes based on versioning.

References

  1. "The Open Closed Principle," Robert C. Martin, C++ Report, January 1996
  2. Todd Veldhuizen: "Template Metaprograms" http://osl.iu.edu/~tveldhui/papers/Template-Metaprograms/meta-art.html
  3. C++ Templates: The Complete Guide, by David Vandevoorde, Nicolai M. Josuttis. Addison-Wesley Professional, 2002
  4. Design Patterns, Gamma, et. al, Addison-Wesley, 1995




Page 2 of 2



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel