You are here:

C++/How to use type discrimination?

Advertisement


Question
hello Ralph,

I was going thru your answer for following question,

http://en.allexperts.com/q/C-1040/c-c-Overloading-problem.htm?zIr=5

In the last paragraph you mentioned that we can use a "type discrimination value" to determine the type of template class. I am having trouble figuring it out.

I am trying to write a generic add function that will take any type of object and at runtime based on the type of object will decide the action. For e.g.

// This example follows your article
class ValueBase
{
 public:
   virtual ~ValueBase();

};

// Implementation
ValueBase::~ValueBase()
{
}

template <class T>
class Value : public ValueBase {

 public:
   explicit Value(T valueIn) : value_(valueIn) {}

   virtual ~Value();

   T getValue(){
     return value_;
   }

 private:
   T value_;

};

template <class T>
Value<T>::~Value(){
}

template <class T>
bool checkType (ValueBase* vBase) {
 Value<T> *val = dynamic_cast<Value<T> *>( vBase );
 if (! val){
   return false;
 }
 return true;
}


void add(ValueBase* a, ValueBase* b){

  if (checkType<int>(a) && checkType<int>(b)){
      Value<int> *val1 = dynamic_cast<Value<int> *>( a );
      Value<int> *val2 = dynamic_cast<Value<int> *>( b );

      cout << val1->getValue() + val2->getValue() << endl;
   }
}


If there is a better way to do this, please let me know. Thanks!.

Answer
Ah! Well I was only speculating in my answer and had not thought out any specific scheme for the situation specific to the questioner. However the basic idea is simple and is the sort of thing used with discriminated unions. For example here is a simple Any type along these lines:

   class Any
   {
   public:
       enum ValueType { v_char, v_short, v_int, v_long, v_float, v_double };

       Any( char v )   : type_(v_char),   c_(v) {}
       Any( short v )  : type_(v_short),  s_(v) {}
       Any( int v )    : type_(v_int),    i_(v) {}
       Any( long v )   : type_(v_long),   l_(v) {}
       Any( float v )  : type_(v_float),  f_(v) {}
       Any( double v ) : type_(v_double), d_(v) {}

       char GetChar()
       {
         if ( type_ != v_char )
         {
         throw std::logic_error( "Any: Not holding char value"
         " when char value requested."
         );
         }
         return c_;
       }

       short GetShort()
       {
         if ( type_ != v_short )
         {
         throw std::logic_error( "Any: Not holding short value"
         " when short value requested."
         );
         }
         return s_;
       }
   
   // etc...

       ValueType GetValueType() { return type_; }

   private:
       ValueType   type_;
       union
       {
         char    c_;
         short   s_;
         int     i_;
         long    l_;
         float   f_;
         double  d_;
       };
   };

The idea is that together with the value stored in Any instances a value is stored to indicate what type is currently being held. In C++ we can improve on the basic C-struct/union version (the private parts in the Any class) by having a set of constructors that force the setting of the type member, and a set of member functions that check the type requested is the type held in the union.

My suggestion was that such a value can be useful when trying to determine exactly what you have on the stack when you do not know, but whatever it is drives the processing - so if you find an int you do one thing if you find a char value you do another...of course at this point maybe a better interface should be used, quite possibly making use of polymorphism (virtual functions).

The problem with this scheme is making it generic and easily extensible as in the scheme I showed in the answer you reference and your code seems based on. On the other hand it does allow us to easily query the type of the value held (as in the GetValueType member function of the Any example).

An alternative may be to use the std::type_info class (include <typeinfo>), returned by a typeid(type) expression, although I have only some partially worked out ideas on how just off the top of my head (sorry). The type_info class is somewhat limited in what you can do with its instances. They can be compared for equality/in-equality and have a before predicate to determine if one type_info instance represents a type that is notionally before another. What before means is implementation dependent and seems mainly for use in situations where the collation order is important such as storing type_info instances in associative or sorted containers. They also have a name member function that returns a zero terminated byte string. However this value is also implementation defined so could be the same string for different types - one possibility being that all instances return an empty string - so this looks less useful than might at first have been hoped.

The most obvious immediate use of type_info and/or typeid would be to use typeid in your checkType function template:

   template <class T>
   bool checkType (ValueBase* vBase)
   {
       return typeid(*vBase)==typeid(Value<T>);
   }

It may be worth inlining it as well as it is now so simple!

Again all these checks are well and good providing you know the types you are using at compile time.

Another idea would be to store typeids in a map (or store instances of a type wrapping such values to ensure standard library container element requirements are met, if using say std::map<>). Each Value pointer your program uses could then be used as a key into this map. The value returned would be an instance of some type (or a pointer to a base instance of some type) that provided an interface to make use of the Value. I tried to do this and met with some problems:

In order to wrap a type_info object so that it meets the requirements of a std::map, instances of the wrapper class need to be copyable and assignable (i.e. have a copy constructor and an assignment operator), and be default constructable. The easiest (quickest) way I could find to do this was to store a pointer to the type_info object returned by typeid for the type passed to a member-template constructor. However I found that some compilers do not use a plain pointer for type_info objects - MS VC++ 8.0 (a.k.a. 2005) for example qualified the pointer type with a compiler-specific __w64 attribute (g++ 4.0.2 did not have any such silliness however).

Also the wrapper class should have at least wrapper operator functions for operator==, operator!= and operator< (calls the type_info::Before member function). I added operator overloads for >, <= and >= for completeness.

All in all I felt that the resultant implementation was somewhat lacking in elegance - and possibly relied on either compiler implementation specific or (worse) undefined behaviour, although I could not find anything explicit in the standard document. Obviously more thought is needed to see if a better solution were available.

Another option would be to move the action handling functionality towards the Value<> types themselves, such that the handing behaviour were stored with the value data. Of course this implies that the action behaviour is known when the values are created - which it may well may not be.

Here is some of my code for the separate type_info, action map, less the implementation details of the TypeInfoWrapper class.

First, the Action class(es). This is similar in approach to the ValueBase class and Value class template:

   class ActionBase
   {
   public:
       virtual void HandleValue( ValueBase * v )=0;
       virtual ~ActionBase();
   };

   ActionBase::~ActionBase()
   {
   }

   // Default action does nothing
   template <typename T>
   class Action : public ActionBase
   {
   public:
       virtual void HandleValue( ValueBase * v );
   };

   template <typename T>
   void Action<T>::HandleValue( ValueBase * v )
   {
   }

The above takes care of types we do not have any specific actions for. For any type that does have some handler behaviour we explicitly and fully specialise Action, as in my example for char:

   // Full Specialisation for actions for char values
   template <>
   class Action<char> : public ActionBase
   {
   public:
       virtual void HandleValue( ValueBase * v );
   };

   void Action<char>::HandleValue( ValueBase * v )
   {
       if ( !checkType<char>(v) )
       {
         throw std::logic_error( "Non-char value passed to Action"
         " handler for char values"
         );
       }

       std::cout << "Value is a '"
         << dynamic_cast<Value<char>*>(v)->getValue()
         << "' character\n"
         ;
   }

Next the type_info class wrapper class is defined. As I mentioned I am not happy with my implementation, so I show it here only as a starting point to give you the general idea. Also I have not tested all the operators so I may have slipped up with the logic or parenthesis placement.

At the start is a quick kludge to define a type alias for the pointer type I am using for pointers to type_info objects returned by typeid to cope with the MS specific __w64 qualification for Visual C++ (again this is a quick and dirty test - it does not take into account which version of VC++, and assumes that all other compilers use a normal pointer):

   #ifdef _MSC_VER
       typedef std::type_info const * __w64 TypeIdPtr;
   #else
       typedef std::type_info const *       TypeIdPtr;
   #endif

The required constructors are the default constructor which just sets the type_info pointer member to a null pointer value, and the copy constructor for which the compiler provided default is sufficient - oddly as this class has a pointer member. The assignment operator is also required and again the compiler provided default it relied on. The class need no special destructor logic so (again) I have not explicitly defined one.

The other constructor is for our code's benefit and is in the form of a member function template. It takes a single parameter of type T by const reference which it passes to typeid to obtain the type_info instance whose pointer is stored in the TypeInfoWrapper instance.

Next there are the comparison operators. The == and != operators simple pass through to the operators of the type_info class. The < and > operators are wrappers for the type_info before member function and <= and >= are the negation of > and < respectively. For a std::map using the default sorting criterion of < it is best to have at least the ==, != and < operators defined.

   class TypeInfoWrapper
   {
   public:
       TypeInfoWrapper() : ti_(0) {}

       template <class T>
       explicit TypeInfoWrapper(T const & v) : ti_(&typeid(v)) {}

       bool operator==(TypeInfoWrapper const & rhs) const
       {
         return *ti_==*(rhs.ti_);
       }

       bool operator!=(TypeInfoWrapper const & rhs) const
       {
         return *ti_!=*(rhs.ti_);
       }

       bool operator<(TypeInfoWrapper const & rhs) const
       {
         return ti_->before(*(rhs.ti_));
       }

       bool operator>(TypeInfoWrapper const & rhs) const
       {
         return rhs.ti_->before(*ti_);
       }

       bool operator>=(TypeInfoWrapper const & rhs) const
       {
         return ! (*this<rhs);
       }

       bool operator<=(TypeInfoWrapper const & rhs) const
       {
         return !(*this>rhs);
       }

       TypeIdPtr ti_;
   };

Instances of TypeInfoWrapper can be used as keys to a std::map, which are associated with pointers to ActionBase instances:

   typedef std::map<TypeInfoWrapper, ActionBase*>  ValueTypeActionMapType;

We can then use all this like so:

   // Set up type_info, action map:
   ValueTypeActionMapType map;

   Action<char>  charAction;
   map.insert( std::make_pair( TypeInfoWrapper(Value<char>(char()))
         , &charAction
         )
         );
 
   Action<int>   intAction;
   map.insert( std::make_pair( TypeInfoWrapper(Value<int>(int()))
         , &intAction
         )
         );

The above simply defines a ValueTypeActionMapType instance called map and then creates an Action<> class template instance for char and adds it to map associated with the wrapped type_info for Value<char> types - the char() parameter simply passes a zero valued char to the Value<char> constructor (as it has no default constructor). This sequence is then repeated for actions for int values.

Next we use the map to execute some actions for Value<> class template instances:

   // Now do some stuff with the map:

   // First try doing actions for int values.
   // As there are no specialised actions for handling int values the
   // default, non-specialised Action<T> template is used and so no actions
   // should be performed.
   Value<int> vi(1);
   ValueBase * pVb( &vi );
   std::cout << "Doing actions for vi:\n";
   map[TypeInfoWrapper(vi)]->HandleValue(pVb);

   // Now try doing actions for char values.
   // As there are specialised actions for handling char values the full
   // specialisation of the Action template, Action<char> is used and we
   // expect some output telling us what character the Value<char> holds.
   Value<char> vc('a');
   pVb = &vc;
   std::cout << "Doing actions for vc:\n";
   map[TypeInfoWrapper(vc)]->HandleValue(pVb);

However this is not that useful as I am looking up the actions associated with a value type by using a TypeInfoWrapper instance key constructed directly from the explicit Value types. What we need to be able to do is use the ValueBase pointer pVb in the:

   map[TypeInfoWrapper(vi)]->HandleValue(pVb);
and:
   map[TypeInfoWrapper(vi)]->HandleValue(pVb);

statements in place of vi and vc, as it is almost certain that in real code this is all you will have access to:

   map[TypeInfoWrapper(*pVb)]->HandleValue(pVb);
and:
   map[TypeInfoWrapper(*pVb)]->HandleValue(pVb);

And indeed this can be done providing TypeInfoWrapper is implemented correctly (that is take the typeid of the parameter passed to the constructor and not its type):

   Value<int> vi(1);
   ValueBase * pVb( &vi );

   std::cout << "Doing actions for vi:\n";
   map[TypeInfoWrapper(*pVb)]->HandleValue(pVb);

   Value<char> vc('a');
   pVb = &vc;

   std::cout << "Doing actions for vc:\n";
   map[TypeInfoWrapper(*pVb)]->HandleValue(pVb);

The above works just as the previous examples - the actions for the Value<int> do nothing whereas the actions for Value<char> output the expected text:

   Value is a 'a' character

(OK so that should be "Value is an 'a' character" but this is just a simple example!).

Hope this gives you some ideas and starting points. I would strongly suggest you do _not_ use the code shown here verbatim as it is just an example and not meant to be production quality in any way at all. However I think with a little more thought you should be able to tidy up these ideas and come up with something more robust.

As mentioned I used the MS VC8.0 (32 bit) compiler and GNU g++ 4.0.2 under 64-bit Linux to test the code.

Have fun and please ask further questions if you require further explanations.  

C++

All Answers


Answers by Expert:


Ask Experts

Volunteer


Ralph McArdell

Expertise

I am a software developer with more than 15 years C++ experience and over 25 years experience developing a wide variety of applications for Windows NT/2000/XP, UNIX, Linux and other platforms. I can help with basic to advanced C++, C (although I do not write just-C much if at all these days so maybe ask in the C section about purely C matters), software development and many platform specific and system development problems.

Experience

My career started in the mid 1980s working as a batch process operator for the now defunct Inner London Education Authority, working on Prime mini computers. I then moved into the role of Programmer / Analyst, also on the Primes, then into technical support and finally into the micro computing section, using a variety of 16 and 8 bit machines. Following the demise of the ILEA I worked for a small company, now gone, called Hodos. I worked on a part task train simulator using C and the Intel DVI (Digital Video Interactive) - the hardware based predecessor to Indeo. Other projects included a CGI based train simulator (different goals to the first), and various other projects in C and Visual Basic (er, version 1 that is). When Hodos went into receivership I went freelance and finally managed to start working in C++. I initially had contracts working on train simulators (surprise) and multimedia - I worked on many of the Dorling Kindersley CD-ROM titles and wrote the screensaver games for the Wallace and Gromit Cracking Animator CD. My more recent contracts have been more traditionally IT based, working predominately in C++ on MS Windows NT, 2000. XP, Linux and UN*X. These projects have had wide ranging additional skill sets including system analysis and design, databases and SQL in various guises, C#, client server and remoting, cross porting applications between platforms and various client development processes. I have an interest in the development of the C++ core language and libraries and try to keep up with at least some of the papers on the ISO C++ Standard Committee site at http://www.open-std.org/jtc1/sc22/wg21/.

Education/Credentials

©2016 About.com. All rights reserved.