Improved const_cast<T>(..) definition in C++

A few days ago I talked about how to define const_cast<T>(..) as a templated function. My solution could not cast the const out of a data-member pointer however, because I did not have the type traits functions needed for the job.

But yesterday I posted code to supply type-traits style functions to manipulate data-member pointers which I can use to improve my implementation of const_cast<T>. This post is a listing of that improved implementation, which is called const_cast_x<T>.

I start with the type-traits template functions.

# include <boost/type_traits.hpp>
using boost::is_same;
using boost::is_pointer;
using boost::is_reference;
using boost::is_member_object_pointer;
using boost::add_const;
using boost::add_volatile;
using boost::add_cv;
using boost::add_pointer;
using boost::add_reference;
using boost::remove_const;
using boost::remove_volatile;
using boost::remove_cv;
using boost::remove_pointer;
using boost::remove_reference;

using boost::mpl::identity;
using boost::mpl::if_;

I also need the following template functions from my previous post.

The improved const_cast_x<T> follows. Most of it is just copied from my earlier post. I have identified the changed parts in code comments, and I have taken out all the BOOST_MPL_ASSERT(..) and other explanatory and testing code in order to keep it brief. There are only two new template functions and two functions that have changed from the earlier version.

// ================================================
// strip_out_cv<T>

// strip_out_member_obj_ptr_cv<T>
// new, not in earlier code listing
template< typename T >
struct strip_out_member_obj_ptr_cv
: if_<
    is_member_object_pointer< T >,
    remove_member_object_pointer_cv< T >,
    identity< T >
  >::type
{ };

// strip_out_cv_no_ref<T>
// no change from earlier code listing
template< typename T, bool IS_PTR = is_pointer< T >::value >
struct strip_out_cv_no_ref
: add_pointer<
    typename strip_out_cv_no_ref<
      // remove_pointer<T> will remove top-level
      // const and volatile if they are present.
      typename remove_pointer<
        T
      >::type
    >::type
  >
{ };

// strip_out_cv_no_ref<T> specialization
// changed from earlier code listing
template< typename T >
struct strip_out_cv_no_ref< T, false >
: strip_out_member_obj_ptr_cv<
    typename remove_cv< T >::type
  >
{ };

// strip_out_cv_maybe_ref<T>
// no change from earlier code listing
template< typename T, bool IS_REF = is_reference< T >::value >
struct strip_out_cv_maybe_ref
: add_reference<
    typename strip_out_cv_no_ref<
      typename remove_reference<
        T
      >::type
    >::type
  >
{ };

// strip_out_cv_maybe_ref<T> specialization
// no change from earlier code listing
template< typename T >
struct strip_out_cv_maybe_ref< T, false >
: strip_out_cv_no_ref< T >
{ };

// strip_out_cv<T>
// no change from earlier code listing
template< typename T >
struct strip_out_cv
: strip_out_cv_maybe_ref< T >
{ };

// ================================================
// load_with_cv<T>

// load_with_member_obj_ptr_cv<T>
// new, not in earlier code listing
template< typename T >
struct load_with_member_obj_ptr_cv
: if_<
    is_member_object_pointer< T >,
    add_member_object_pointer_cv< T >,
    identity< T >
  >::type
{ };

// load_with_cv_no_ref<T>
// no change from earlier code listing
template< typename T, bool IS_PTR = is_pointer< T >::value >
struct load_with_cv_no_ref
: add_cv<
    typename add_pointer<
      typename load_with_cv_no_ref<
        typename remove_pointer<
          T
        >::type
      >::type
    >::type
  >
{ };

// load_with_cv_no_ref<T> specialization
// changed from earlier code listing
template< typename T >
struct load_with_cv_no_ref< T, false >
: add_cv<
    typename load_with_member_obj_ptr_cv< T >::type
  >
{ };

// load_with_cv_maybe_ref<T>
// no change from earlier code listing
template< typename T, bool IS_REF = is_reference< T >::value >
struct load_with_cv_maybe_ref
: add_reference<
    typename load_with_cv_no_ref<
      typename remove_reference<
        T
      >::type
    >::type
  >
{ };

// load_with_cv_maybe_ref<T> specialization
// no change from earlier code listing
template< typename T >
struct load_with_cv_maybe_ref< T, false >
: load_with_cv_no_ref< T >
{ };

// load_with_cv<T>
// no change from earlier code listing
template< typename T >
struct load_with_cv
: load_with_cv_maybe_ref< T >
{ };

// ================================================
// const_cast_x<T>

// const_cast_x<T>
// no change from earlier code listing
template< typename TRG >
inline
TRG
const_cast_x( typename load_with_cv< TRG >::type a)
{
  return (typename strip_out_cv< TRG >::type) a;
}

Finally, here is some code that confirms that this improved const_cast_x<T> now works like const_cast<T> when casting data-member pointers.

// Define type a_class.
class a_class { public: int a_data_member; };

// Define non-const data-member pointer type.
typedef
  int a_class::*
  ptr_non_c_member_obj_type; /* new type name */

// Define const data-member pointer type.
typedef
  const int a_class::*
  ptr_const_member_obj_type; /* new type name */

// Get a pointer to a non-const member object.
ptr_non_c_member_obj_type
  p_non_c_memb =
    & a_class::a_data_member;

// You can assign a pointer to a non-const
// to a pointer to a const member object
// without casting.
ptr_const_member_obj_type
  p_const_memb =
    p_non_c_memb;

// You have to cast to assign a const
// to a non-const member-object pointer.
ptr_non_c_member_obj_type
  p_non_c_memb2 =
    const_cast<
      ptr_non_c_member_obj_type
    >( p_const_memb);

// const_cast_x<T> can also perform
// this cast with the modifications
// described above.
ptr_non_c_member_obj_type
  p_non_c_memb3 =
    const_cast_x<
      ptr_non_c_member_obj_type
    >( p_const_memb);

In closing, let me reiterate that it should be a goal to define const_cast<T> and the other primitive casting functions in C++. If the casts cannot be expressed as functions then we have found an expressive weakness in C++. And the functional definitions will give us an exact definition for these casts based on the C-style cast.

member_object_pointer_traits

Yesterday I wrote about const_cast<T>(..) and how to define it in C++. But I did not make it work with data-member pointers because the Boost Type Traits library does not supply traits for data member objects. Here I’ll show you how to write type-traits and supporting template functions for data members. We’ll define the following templates:

We’ll start with the headers.

# include <boost/type_traits.hpp>
using boost::is_same;
using boost::add_const;
using boost::add_volatile;
using boost::add_cv;
using boost::remove_const;
using boost::remove_volatile;
using boost::remove_cv;
using boost::remove_pointer;

# include <boost/mpl/assert.hpp>
// BOOST_MPL_ASSERT(..)
// BOOST_MPL_ASSERT_NOT(..)

Boost (1_36) already provides these template functions that deal with members.

Using a similar naming convention we’ll define member_object_pointer_traits<T>. Something like this will probably be part of Boost soon, along with member_function_pointer_traits<T>.

// Declare member_object_pointer_traits but do not
// define a body. The struct will only ever be
// instantiated through specialization.
template< typename DATA_MEMBER_PTR >
struct member_object_pointer_traits;

// Specialized member_object_pointer_traits that
// only matches member-object pointers.
template< typename T, typename C >
struct member_object_pointer_traits< T (C::*) >
{
  typedef C class_type;
  typedef T data_type;
};

We’d also like to be able to manipulate member-object pointers using functions like these:

But remove_pointer<T> cannot remove the pointer from member-object pointer types because the result is not a valid type. Also, add_const<T> and remove_const<T> cannot add/remove const that is buried in the member-object pointer.

// Define type a_class.
class a_class { public: int a_data_member; };

// Define non-const data-member pointer type.
typedef
  int a_class::*
  ptr_non_c_member_obj_type; /* new type name */

// Define const data-member pointer type.
typedef
  const int a_class::*
  ptr_const_member_obj_type; /* new type name */

// Confirm that regular type-traits functions
// do not work with member-object pointer types.

// remove_pointer<T> does nothing
BOOST_MPL_ASSERT( (is_same<
      remove_pointer<
    ptr_non_c_member_obj_type
      >::type,
    ptr_non_c_member_obj_type
>));

// remove_const<T> does nothing
BOOST_MPL_ASSERT( (is_same<
      remove_const<
    ptr_const_member_obj_type
      >::type,
    ptr_const_member_obj_type
>));

// add_const<T> adds const outside the
// type and does not affect the inner const.
BOOST_MPL_ASSERT( (is_same<
      add_const<
    ptr_non_c_member_obj_type
      >::type,
      const
    ptr_non_c_member_obj_type
>));

We can define special add/remove functions that work just with member-object pointer types using member_object_pointer_traits<T> which we defined above.

# define DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( PRE, POST ) \
    template< typename T >                                  \
    struct PRE ## _member_object_pointer_ ## POST           \
    {                                                       \
      typedef member_object_pointer_traits< T >             \
        traits_type;                                        \
                                                            \
      typedef typename traits_type::class_type              \
        class_type;                                         \
                                                            \
      typedef typename traits_type::data_type               \
        data_type;                                          \
                                                            \
      typedef typename PRE ## _ ## POST< data_type >::type  \
        adjusted_data_type;                                 \
                                                            \
      /* Construct a member-object-pointer type */          \
      /* around the adjusted inner type.        */          \
      typedef adjusted_data_type (class_type::*             \
        type);                                              \
    }

// remove_member_object_pointer_const<T>
// remove_member_object_pointer_volatile<T>
// remove_member_object_pointer_cv<T>
DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( remove, const   );
DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( remove, volatile);
DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( remove, cv      );

// add_member_object_pointer_const<T>
// add_member_object_pointer_volatile<T>
// add_member_object_pointer_cv<T>
DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( add, const   );
DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( add, volatile);
DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( add, cv      );

# undef DEFINE_MEMBER_OBJECT_POINTER_FUNCTION

// Test some of the above template functions:

BOOST_MPL_ASSERT( (is_same<
      remove_member_object_pointer_const<
    ptr_const_member_obj_type
      >::type,
    ptr_non_c_member_obj_type
>));

BOOST_MPL_ASSERT( (is_same<
      add_member_object_pointer_const<
    ptr_non_c_member_obj_type
      >::type,
    ptr_const_member_obj_type
>));

And that’s how we can define template functions for working with member-object pointers in the Boost style. The traits for member-function pointers will resemble the function-traits class and will be more complicated.

We’ll use the templates we’ve defined here in the next post, when we extend our version of const_cast<T>(..) to work with member-object-pointers.

Defining const_cast<T>(..) as a templated function

A few posts ago I wrote about cast_away_const(..), a function that removes the top-level const from a variable’s type. Unlike const_cast<T>(..), cast_away_const(..) can be called without specifying the return type as a template argument.

Of course const_cast<T>(..) is part of the C++ language and is provided by the compiler, so you don’t have to #include anything to get it. It’s not supposed to be a function and is not specified in functional terms. Until recently you couldn’t even simulate it as a function.

But that’s no longer true. The template language supported by modern compilers is a lot more sophisticated than it used to be, and you can now define your own const_cast<T>(..) to safely wrap a raw C-style casting. In this post I’ll show you how.

I’ll also argue that since const_cast<T>(..) can be defined as a function, it SHOULD be defined as a function. The implementation and test suite would then become the specification. C++ should be defined in C++ whenever possible.

To define our own const_cast<T>(..), we’ll build on Type Traits and MPL from Boost.

# include <boost/type_traits.hpp>
using boost::is_same;
using boost::is_const;
using boost::is_volatile;
using boost::is_pointer;
using boost::is_reference;
using boost::add_cv;
using boost::add_pointer;
using boost::add_reference;
using boost::remove_cv;
using boost::remove_pointer;
using boost::remove_reference;

# include <boost/mpl/logical.hpp>
# include <boost/mpl/int.hpp>
using boost::mpl::bool_;
using boost::mpl::true_;
using boost::mpl::false_;
using boost::mpl::and_;
using boost::mpl::or_;
using boost::mpl::not_;

# include <boost/mpl/assert.hpp>
// BOOST_MPL_ASSERT(..)
// BOOST_MPL_ASSERT_NOT(..)

Let’s start with something simpler, has_const_somewhere<T>, which will give you an idea of how to implement const_cast<T>(..). In the following implementation, has_const_somewhere<T> starts with a type that looks something like int***&, and it peels off the layers, first removing the outer reference and then stripping off the pointers one by one, all the while looking for const. We’ll need something like that for const_cast<T>(..).

// Helper for has_const_somewhere<T>
template< typename T >
struct has_const_somewhere_no_ref
: or_<
    is_const< T >, /* stop when we hit const */
    and_<
      is_pointer< T >,
      has_const_somewhere_no_ref<
        typename remove_pointer< T >::type
      >
    >
  >
{ };

// has_const_somewhere<T>
template< typename T >
struct has_const_somewhere
: has_const_somewhere_no_ref<
    typename remove_reference< T >::type
  >
{ };


// Test has_const_somewhere<T>
BOOST_MPL_ASSERT( (has_const_somewhere< const int& >));
BOOST_MPL_ASSERT( (has_const_somewhere< const int***** >));
BOOST_MPL_ASSERT( (has_const_somewhere< const int* const * >));
BOOST_MPL_ASSERT_NOT( (has_const_somewhere< int*****& >));

// Confirm that has_const_somewhere<T> evals to true_ or
// false_, and not just to a type with a bool-looking T::value.
BOOST_MPL_ASSERT( (is_same<
    true_,
    has_const_somewhere< const int >::type >));
BOOST_MPL_ASSERT( (is_same<
    false_,
    has_const_somewhere< int**& >::type >));

We’ll define strip_out_cv<T> in a similar way. strip_out_cv<T> removes all the const and volatile markers from a type of the form T*****& by peeling back all the layers and then putting them back again, but without const and volatile. Remember that const_cast<T>(..) deals with volatile as well as const.

// strip_out_cv_no_ref<T> recursive type.
// Removes outer pointer (along with any cv attached),
// removes all the inner cv markers, and puts the outer
// pointer back on again.
template< typename T, bool IS_PTR = is_pointer< T >::value >
struct strip_out_cv_no_ref
: add_pointer<
    typename strip_out_cv_no_ref<
      // remove_pointer<..> will remove top-level
      // const and volatile if they are present.
      typename remove_pointer<
        T
      >::type
    >::type
  >
{ };

// Specialization when IS_PTR is false
template< typename T >
struct strip_out_cv_no_ref< T, false >
: remove_cv< T >
{ };

// strip_out_cv_maybe_ref<T>
// Strips off the outer ref (&), removes all inner const, and
// puts the outer ref back on again.
template< typename T, bool IS_REF = is_reference< T >::value >
struct strip_out_cv_maybe_ref
: add_reference<
    typename strip_out_cv_no_ref<
      typename remove_reference<
        T
      >::type
    >::type
  >
{ };

// Specialization when there is no outer ref.
template< typename T >
struct strip_out_cv_maybe_ref< T, false >
: strip_out_cv_no_ref< T >
{ };

// strip_out_cv<T>
// Wraps up the above template functions.
template< typename T >
struct strip_out_cv
: strip_out_cv_maybe_ref< T >
{ };


// Test strip_out_cv<T>
# define ASSERT_STRIP_CV( AFTER, BEFORE) \
    BOOST_MPL_ASSERT( (is_same< AFTER,   \
      strip_out_cv< BEFORE >::type > ))

ASSERT_STRIP_CV( int, const int);
ASSERT_STRIP_CV( int, volatile int);
ASSERT_STRIP_CV( int*&,
    const volatile int* volatile const &);

ASSERT_STRIP_CV( char****&,
    char* const * volatile **&);

ASSERT_STRIP_CV( char******, char******);
ASSERT_STRIP_CV( char******,
    volatile char* const * volatile const **** const);

# undef ASSERT_STRIP_CV

Now we’ll define load_with_cv<T> which is the opposite of strip_out_cv<T>. Instead of removing them, load_with_cv<T> adds const and volatile markers to a type whereever possible. load_with_cv< int*& >::type ends up as const volatile int* const volatile &.

// load_with_cv_no_ref<T> recursive type.
// Removes outer pointer, loads up the rest with cv,
// and puts the outer pointer back on again this time
// marked as both const and volatile.
template< typename T, bool IS_PTR = is_pointer< T >::value >
struct load_with_cv_no_ref
: add_cv<
    typename add_pointer<
	  // recursive type definition
      typename load_with_cv_no_ref<
        typename remove_pointer<
          T
        >::type
      >::type
    >::type
  >
{ };

// Specialization when T is not a pointer
template< typename T >
struct load_with_cv_no_ref< T, false >
: add_cv< T >
{ };

// load_with_cv_maybe_ref<T>
// Strips off the outer ref (&), marks as both
// const and volatile, and puts the outer ref
// back on again.
template< typename T, bool IS_REF = is_reference< T >::value >
struct load_with_cv_maybe_ref
: add_reference<
    typename load_with_cv_no_ref<
      typename remove_reference<
        T
      >::type
    >::type
  >
{ };

// Specialization when T is not a reference.
template< typename T >
struct load_with_cv_maybe_ref< T, false >
: load_with_cv_no_ref< T >
{ };

// load_with_cv<T>
// Wraps up the above templates.
template< typename T >
struct load_with_cv
: load_with_cv_maybe_ref< T >
{ };


// Test load_with_cv<T>
# define ASSERT_LOAD_CV( BEFORE, AFTER)  \
    BOOST_MPL_ASSERT( (is_same< AFTER,   \
      load_with_cv< BEFORE >::type > ))

ASSERT_LOAD_CV( int, const volatile int);
ASSERT_LOAD_CV( int, volatile const int);
ASSERT_LOAD_CV( int**&,
    volatile const int*
    const volatile *
    const volatile &);

// The original type can start with const
// and volatile markers already in it.
ASSERT_LOAD_CV( const volatile int**&,
    volatile const int*
    const volatile *
    const volatile &);

# undef ASSERT_LOAD_CV

Now we have all the tools we need to define our own const_cast<T>(..), which we’ll call const_cast_x<T>(..).

// const_cast_x<T>(..)
// replacement for const_cast<T>(..)
template< typename TRG >
inline
TRG
const_cast_x( typename load_with_cv< TRG >::type a)
{
    return (typename strip_out_cv< TRG >::type) a;
}

Yeah! We can test it, and it works, at least with msvc++9.0 and g++3.4.5. All of the following compile with no complaint.

// Test const_cast_x<T>(..)
# ifndef NDEBUG

const int  ca  = 0;
const int& rca = ca;

const int* const  pca  = &ca;
const int* const& rpca = pca;

const int*
  volatile const * const
     ppca = &pca;
volatile const int*
  const volatile * const&
     rppca = ppca;

int& a1 = const_cast_x< int& >( ca);
int& a2 = const_cast_x< int& >( rca);
int& a3 = const_cast_x< int& >( *pca);
int& a4 = const_cast_x< int& >( *rpca);
int& a5 = const_cast_x< int& >( **ppca);
int& a6 = const_cast_x< int& >( **rppca);

int* pa1 = const_cast_x< int* >( &ca);
int* pa2 = const_cast_x< int* >( &rca);
int* pa3 = const_cast_x< int* >( pca);
int* pa4 = const_cast_x< int* >( rpca);
int* pa5 = const_cast_x< int* >( *ppca);
int* pa6 = const_cast_x< int* >( *rppca);

int** ppa3 = const_cast_x< int** >( &pca);
int** ppa4 = const_cast_x< int** >( &rpca);
int** ppa5 = const_cast_x< int** >( ppca);
int** ppa6 = const_cast_x< int** >( rppca);

int*** pppa5 = const_cast_x< int*** >( &ppca);
int*** pppa6 = const_cast_x< int*** >( &rppca);

# endif /* ifndef NDEBUG */

This proves that it’s possible to write a close approximation to const_cast<T>(..) using templates. But we cannot specify exactly what const_cast<T>(..) is supposed to do by writing it in C++ yet because const_cast_x<T>(..) is not exactly right.

First of all, const_cast<T>(..) can cast away const in a data-member pointer while const_cast_x<T>(..) cannot.

struct MyStruct { int v; };

// const data member
const int MyStruct::*
    p_const_data_member = &MyStruct::v;

// no compiler error/warning
int MyStruct::*
    p_data_member1 =
        const_cast< int MyStruct::* >( p_const_data_member);

// compiler error!
int MyStruct::*
    p_data_member2 =
        const_cast_x< int MyStruct::* >( p_const_data_member);

I know how to get the const out of const int MyStruct::*, and can fix this inconsistency. This post is already too long though, so I’ll talk about it in another post.

Also consider the following which fail if you use the compiler-supplied const_cast<T>(..) but are OK if you use const_cast_x<T>(..).

# ifndef NDEBUG

// These do not work with const_cast<T>(..).
int ca1 = const_cast_x< int >( ca);
int ca2 = const_cast_x< const int >( ca);
const volatile
int ca3 = const_cast_x< const int >( ca);

# endif /* ifndef NDEBUG */

These fail because casting is not necessary, and because you are only supposed to const_cast<T>(..) pointers, refs, and data-member pointers. const_cast_x<T>(..) could be fixed to work correctly here.

Or consider the following. All of these compile on msvc++9.0, but they all fail on g++3.4.5 (except for rpa6 and rppa6, which I cannot explain). Almost all of these work with both compilers if you use const_cast<..>(..) instead of const_cast_x<..>(..), although a few have r-value problems.

// Test const_cast_x<T>(..)
//
// All of these work with msvc++9.0.
// Most of these fail with g++3.4.5.
// These mostly work if you use const_cast<T>(..)
// instead of const_cast_x<T>(..).

# ifndef NDEBUG

int*& rpa1 = const_cast_x< int*& >( &ca);
int*& rpa2 = const_cast_x< int*& >( &rca);
int*& rpa3 = const_cast_x< int*& >( pca);
int*& rpa4 = const_cast_x< int*& >( rpca);
int*& rpa5 = const_cast_x< int*& >( *ppca);
int*& rpa6 = const_cast_x< int*& >( *rppca);

int**& rppa3 = const_cast_x< int**& >( &pca);
int**& rppa4 = const_cast_x< int**& >( &rpca);
int**& rppa5 = const_cast_x< int**& >( ppca);
int**& rppa6 = const_cast_x< int**& >( rppca);

int***& rpppa5 = const_cast_x< int***& >( &ppca);
int***& rpppa6 = const_cast_x< int***& >( &rppca);

# endif /* ifndef NDEBUG */

The r-value types coming in C++0x will let us refine and correct const_cast_x<..>(..)‘s behavior.

In conclusion, once we have r-values we will probably be able to define const_cast<T>(..) exactly as a C++ function, which should be something we strive for.

We should also have a way, maybe with static_assert(..) or #pragma, to improve compiler error and warning messages, such as those warning us of spurious casts or bindings to temporary values.

I also think it should be a goal to define the other casting forms as C++ functions. It’s almost possible now with reinterpret_cast<T>(..) and static_cast<T>(..). We’d need programmer access to the compiler-generated Run-Time Type Information (RTTI) to implement dynamic_cast<T>(..), so that should be a goal in a future version of C++. And of course polymorphic_cast<T>(..), polymorphic_downcast<T>(..), and numeric_cast<T>(..) are already defined as templated functions.

Next Page →