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.

Recursive inline expansion

Since I’m talking about recursive templates today I thought I’d mention recursive inline expansion. Suppose we define factorial (with no bounds checking) like this.

template< int N > inline int factorial( )
{
    return N * factorial< N - 1 >( );
}
template<> inline int factorial< 0 >( )
{
    return 1;
}

The template language will calculate values of N and only generate the necessary code. If the compiler chose to follow the “inline” request it would generate a good optimization. factorial< 5 >( ) would become the constant (5*4*3*2*1*1), or 120.

Now let’s do the same thing without templates.

inline int factorial( int N)
{
    return (N == 0) ? 1 : (N * factorial( N - 1));
}

An inline expansion of this function could go forever unless N is a compile-time constant and the compiler trims the code by watching for (N==0). You can see something similar when using #define.

// You cannot recurse with #define, but this kludge works.
# define FACTORIAL( N )   (((N)==0)?1:((N)*FACTORIAL_1((N)-1)))
// Helper macros:
# define FACTORIAL_1( N ) (((N)==0)?1:((N)*FACTORIAL_2((N)-1)))
# define FACTORIAL_2( N ) (((N)==0)?1:((N)*FACTORIAL_3((N)-1)))
# define FACTORIAL_3( N ) (((N)==0)?1:((N)*FACTORIAL_4((N)-1)))
# define FACTORIAL_4( N ) (((N)==0)?1:((N)*FACTORIAL_5((N)-1)))
# define FACTORIAL_5( N ) (((N)==0)?1:((N)*FACTORIAL_6((N)-1)))
# define FACTORIAL_6( N ) (((N)==0)?1:((N)*FACTORIAL_7((N)-1)))
# define FACTORIAL_7( N ) (((N)==0)?1:((N)*FACTORIAL_8((N)-1)))
# define FACTORIAL_8( N ) 0

This only works though FACTORIAL( 7). FACTORIAL( 8) is zero. Something trivial like FACTORIAL( 2) expands into a huge mess.

FACTORIAL( 2 ) becomes:

(((2)==0)?1:((2)*
((((2)-1)==0)?1:(((2)-1)*
(((((2)-1)-1)==0)?1:((((2)-1)-1)*
((((((2)-1)-1)-1)==0)?1:(((((2)-1)-1)-1)*
(((((((2)-1)-1)-1)-1)==0)?1:((((((2)-1)-1)-1)-1)*
((((((((2)-1)-1)-1)-1)-1)==0)?1:(((((((2)-1)-1)-1)-1)-1)*
(((((((((2)-1)-1)-1)-1)-1)-1)==0)?1:((((((((2)-1)-1)-1)-1)-1)-1)*
((((((((((2)-1)-1)-1)-1)-1)-1)-1)==0)?1:(((((((((2)-1)-1)-1)-1)-1)-1)-1)*
0))))))))))))))))

Imagine how big that’d be if we defined it all the way through FACTORIAL( 20).

So you see that recursive inline expansion can get out of hand. That’s why the C++ preprocessor doesn’t allow it, and why compilers usually don’t attempt it. But it is not always a bad idea, as the template example shows. Templates force the compiler to look at values and trim code accordingly. Code generation is a wonderful thing, and intelligent code generation is much more useful than simple macro expansion.

The C++ preprocessor – recursive includes

A few posts ago I mentioned trying to calculate factorials using the C/C++ preprocessor. Afterward I remembered back in college trying to use recursive #includes to solve the same problem. My attempt looked something like this.

First I created a file to do the calculation. I’ll call it calc_factorial.h.

// calc_factorial.h
# if ( N < 0 )
#   error Factorial of negative not allowed
# elif ( N == 0 )
#   define FACTORIAL 1
# else
#   define NSAVE N
#   undef N
#   define N (NSAVE - 1)
#   include "calc_factorial.h"
#   define FSAVE FACTORIAL
#   undef FACTORIAL
#   define FACTORIAL (NSAVE * FSAVE)
# endif

Then from another file I tried to #include "calc_factorial.h" to do the calculation.

# define N 5
# include "calc_factorial.h"
// At this point I hoped FACTORIAL would be 120,
// but instead I got a compile error.

Of course this doesn't work, unless N is 0. For other values it reports the #error "Factorial of negative not allowed". Without the (N<0) test it recurses to death, nesting #include "calc_factorial.h" until we hit the stack limit. On all but the first iteration N --> (NSAVE - 1) --> (N - 1) --> (0 - 1) --> -1. Remember that N is undefined when we get to (N - 1) because recursive expansion is not allowed. And #if interprets undefined tokens as zero.

It was a fun experiment anyway.

← Previous PageNext Page →