1. Introduction
Given the following inheritence diagram which you might find in a video game:
Vehicle / \V V Aircraft Watercraft \/ V V SeaPlane
This would be written in C++ as:
class Vehicle { virtual ~ Vehicle ( void ) = default ; }; class Aircraft : virtual public Vehicle {}; class Watercraft : virtual public Vehicle {}; class SeaPlane : public Aircraft , public Watercraft {};
If we create an object of type
, and then cast it to a
reference to its base class
, we can then use
to convert the
to a
, and this is called a
cross-cast or a side-cast, for example:
SeaPlane var ; Aircraft & v1 = var ; Watercraft & v2 = dynamic_cast < WaterCraft &> ( v1 );
If we create an object of type
, and then cast it to a
reference to its base class
, this is called an up-cast. An
up-cast will always succeed, and therefore it makes more sense to use
instead of
for this purpose, or even just
to allow an implicit conversion to take place:
SeaPlane var ; Vehicle & v1 = static_cast < Vehicle &> ( var ); Vehicle & v2 = var ;
The one remaining kind of cast in a class inheritance hierarchy is the
down-cast. This is where we would have a reference or a pointer to a
, and we want to check if we have a
, for instance:
void LogSeaPlane ( Aircraft const & arg ) { if ( dynamic_cast < SeaPlane const *> ( & arg ) ) Logger :: Write ( "We have a seaplane!" ); }
This paper proposes a new more-restrictive form of
which
can only perform a down-cast.
1.1. Usage
class Vehicle { public : virtual ~ Vehicle ( void ) = default ; }; class Aircraft : virtual public Vehicle {}; class Watercraft : virtual public Vehicle {}; class SeaPlane : public Aircraft , public Watercraft {}; auto main ( void ) -> int { Vehicle var ; Watercraft & water = std :: down_cast < Watercraft &> ( var ); Aircraft & a = dynamic_cast < Aircraft &> ( water ); // succeeds Aircraft & b = std :: down_cast < Aircraft &> ( water ); // throws std::bad_cast Aircraft * c = std :: down_cast < Aircraft *> ( & water ); // return nullptr }
2. Possible Implementation
2.1. GNU g++
#include <cassert>// assert #include <concepts>// derived_from #include <typeinfo>// typeid, type_info #include <type_traits>// is_const, is_pointer, is_polymorphic, is_volatile, remove_cvref, remove_pointer #include <tinfo.h>// __class_type_info, vtable_prefix template < typename DstPtr , typename SrcPtr > requires ( std :: is_pointer_v < DstPtr > && std :: is_pointer_v < SrcPtr > ) DstPtr down_cast ( SrcPtr const src_ptr ) { using Dst = std :: remove_pointer_t < DstPtr > ; // might be const volatile using Src = std :: remove_pointer_t < SrcPtr > ; // might be const volatile if ( false== std :: is_polymorphic_v < Dst > ) return nullptr ; // Destination type must be polymorphic if ( false== std :: derived_from < Dst , Src > ) return nullptr ; // Source type must be a public base class of Destination type static_assert ( std :: is_const_v < Dst > || ! std :: is_const_v < Src > ); static_assert ( std :: is_volatile_v < Dst > || ! std :: is_volatile_v < Src > ); using namespace __cxxabiv1 ; __class_type_info const * const dst_type = dynamic_cast < __class_type_info const *> ( & typeid ( std :: remove_cvref_t < Dst > )); __class_type_info const * const src_type = dynamic_cast < __class_type_info const *> ( & typeid ( * src_ptr )); assert ( nullptr != dst_type ); assert ( nullptr != src_type ); if ( __builtin_expect ( ! src_ptr , 0 )) return NULL; // Handle precondition violations gracefully. const void * vtable = * static_cast < const void * const *> ( static_cast < void *> ( src_ptr )); const vtable_prefix * prefix = ( adjust_pointer < vtable_prefix > ( vtable , - ptrdiff_t ( offsetof ( vtable_prefix , origin )))); const void * whole_ptr = adjust_pointer < void > ( src_ptr , prefix -> whole_object ); const __class_type_info * whole_type = prefix -> whole_type ; __class_type_info :: __dyncast_result result ; // If the whole object vptr doesn't refer to the whole object type, we're // in the middle of constructing a primary base, and src is a separate // base. This has undefined behavior and we can't find anything outside // of the base we're actually constructing, so fail now rather than // segfault later trying to use a vbase offset that doesn't exist. const void * whole_vtable = * static_cast < const void * const *> ( whole_ptr ); const vtable_prefix * whole_prefix = ( adjust_pointer < vtable_prefix > ( whole_vtable , - ptrdiff_t ( offsetof ( vtable_prefix , origin )))); if ( whole_prefix -> whole_type != whole_type ) return NULL; whole_type -> __do_dyncast ( -1 , __class_type_info :: __contained_public , dst_type , whole_ptr , src_type , src_ptr , result ); if ( ! result . dst_ptr ) return NULL; if ( contained_public_p ( result . dst2src )) // Src is known to be a public base of dst. return static_cast < DstPtr > ( const_cast < void *> ( result . dst_ptr )); if ( contained_public_p ( __class_type_info :: __sub_kind ( result . whole2src & result . whole2dst ))) // Both src and dst are known to be public bases of whole. Found a valid // cross cast. return NULL; // ---- we do not want a valid cross-cast if ( contained_nonvirtual_p ( result . whole2src )) // Src is known to be a non-public nonvirtual base of whole, and not a // base of dst. Found an invalid cross cast, which cannot also be a down // cast return NULL; if ( result . dst2src == __class_type_info :: __unknown ) result . dst2src = dst_type -> __find_public_src ( -1 , result . dst_ptr , src_type , src_ptr ); if ( contained_public_p ( result . dst2src )) // Found a valid down cast return static_cast < DstPtr > ( const_cast < void *> ( result . dst_ptr )); // Must be an invalid down cast, or the cross cast wasn't bettered return NULL; } template < typename DstRef , typename SrcParam > requires ( std :: is_reference_v < DstRef > && ! std :: is_pointer_v < std :: remove_reference_t < SrcParam > > ) DstRef down_cast ( SrcParam && src ) // src is a forwarding reference { typedef SrcParam && SrcRef ; // Destination and Source must both be Lvalue, or both be Rvalue. static_assert ( std :: is_lvalue_reference_v < DstRef > == std :: is_lvalue_reference_v < SrcRef > && std :: is_rvalue_reference_v < DstRef > == std :: is_rvalue_reference_v < SrcRef > , "Mismatch in Lvalue and Rvalue references between Destination and Source" ); typedef std :: remove_reference_t < DstRef > Dst ; typedef std :: remove_reference_t < SrcRef > Src ; // Destination type must be polymorphic if ( false== std :: is_polymorphic_v < Dst > ) throw std :: bad_cast {}; // Source type must be a public base class of Destination type if ( false== std :: derived_from < Dst , Src > ) throw std :: bad_cast {}; static_assert ( ( std :: is_const_v < Dst > || ! std :: is_const_v < Src > ) && ( std :: is_volatile_v < Dst > || ! std :: is_volatile_v < Src > ), "Mismatch in cv-qualifiers between Destination and Source" ); Dst * const p = down_cast < Dst *> ( & src ); if ( nullptr == p ) throw std :: bad_cast {}; return * p ; }
2.2. Microsoft VC++
. . . TO DO . . .
3. Impact on the Standard
The addition of
constitutes a
library extension that has no impact on existing language features or
behavior.
4. Impact on Existing Code
No existing code becomes ill-formed due to the introduction of
. The behavior of existing code
is unaffected.
5. Acknowledgements
This was Phil Endecott’s idea.