Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 19.  Casts


19.9. boost::polymorphic_cast

Casts don't have to be, and rarely are, as complex as the interface casts. Often they are much less so.

In [Stro1997], Bjarne Stroustrup set an exercise to the reader, asking for an implementation of a template ptr_cast to work like dynamic_cast except that it should throw bad_cast for pointers as well as for references. Well, what he actually said was "Write a template ptr_cast that works like dynamic_cast, except that it throws bad_cast rather than returning 0." Nothing other than the name of the requested entity, ptr_cast, suggests that this works for pointer types only, but that's no doubt the interpretation that was meant and that is generally taken. Boost's polymorphic_cast has the following implementation:



template <class Target, class Source>


inline Target polymorphic_cast(Source* x)


{


  Target tmp = dynamic_cast<Target>(x);


  if ( tmp == 0 ) throw std::bad_cast();


  return tmp;


}



and is used in the following manner:



try


{


  Base    *b = new Base();


  Derived *d = boost::polymorphic_cast<Derived*>(a);


}


catch(std::bad_cast &x)


{


  . . .


}



It seems curious at first to have the argument x be declared as a pointer (i.e., x is Source* rather than Source, or Source &) when that could be deduced by the compiler. But this actually makes sense since by declaring it explicitly as a pointer the cast is constrained from being applied to nonpointer types.

What about if we were to take a stricter interpretation, that is, that ptr_cast can work with reference or pointer types and that in both cases it throws bad_cast when conversion fails? A quick search around the Internet reveals precious few ptr_casts, so I guess the problem must be pretty tricky. The best I could come up with in a morning's (very taxing) effort is shown in Listing 19.20.

Listing 19.20.



template <typename T>


struct ptr_cast


{


public:


  typedef typename base_type_traits<T>::cv_type cv_type;


  typedef cv_type                               &reference;


  typedef cv_type                               *pointer;


public:


  template <typename Source>


  ptr_cast(Source &s)


    : m_p(&dynamic_cast<Target>(s))


  {


    // Nothing to do: dynamic_cast throws for reference types


  }


  template <typename Source>


  ptr_cast(Source *s)


    : m_p(dynamic_cast<Target>(s))


  {


    if(NULL == m_p)


    {


      throw std::bad_cast();


    }


  }


  ptr_cast(pointer_type pt)


    : m_p(t)


  {}


  ptr_cast(reference_type t)


    : m_p(&t)


  {}


public:


  operator reference () const


  {


    return const_cast<reference>(*m_p);


  }


  operator pointer () const


  {


    return const_cast<pointer>(m_p);


  }


protected:


  pointer m_p;


};



The cast uses the base_type_traits template (see section 19.7) to deduce the appropriate cv-qualified base type. This type is then used to derive the pointer and reference types, which are then used to define the implicit conversion operators needed to return the converted value back to the caller. The last part of the picture is that the result of the dynamic_cast applied to a pointer is tested, and bad_cast is thrown if the cast failed and returned NULL.

Now we can use it with pointers and references.



class X


{};


class D


{};


D d;


dynamic_cast<X&>(d);  // Throws bad_cast


ptr_cast<X&>(d);      // Throws bad_cast


dynamic_cast<X*>(&d); // Returns NULL


ptr_cast<X*>(&d);     // Throws bad_cast



The implementation works only for compilers that support partial template specialization, so that rules out Visual C++ (6.0 and 7.0) and Watcom immediately. The real implementation has some workarounds to work with Borland, and has an ambiguity with GCC when passed pointers to intermediates rather than a separate variable. But with CodeWarrior, Comeau, Digital Mars, Intel, and Visual C++ 7.1, it works perfectly in all guises.

Of course it's nicer, where possible, to have a unified way of dealing with pointers and references, but given the almost-but-not-quite nature of this version, we cannot castigate the Boost implementation for constraining itself to deal only with pointers.


      Previous section   Next section