Using BOOST_PP_.. macros to define an array class with constructors

Sometimes when programming you just want an array of values, and you want to be able to construct the individual elements of the array, which isn’t easy with a raw array.

You could use std::tr1::tuple<..>, or even std::pair<..> as your “array”, although it will have to be small (tuples are limited to 10 values). Or you could work with uninitialized bytes and invoke the constructors explicitly with new( pvoid_raw_memory) type( ctor_params) or std::uninitialized_fill_n(..) or allocator<type>.construct(..).

You cannot use boost::array<..> if you want to use the element constructors. boost::array<..> has no constructors (besides the auto-generated copy and default ctors). This design choice was made so boost::array<..> objects can be initialized with the plain-old-data (pod) initializer lists.

You could define a quick class with a bunch of repeated elements that works like an array. Templates and macros make this job easier. This article shows how you can an array-like class with macros.

I’m going to use the Boost Preprocessor BOOST_PP_.. macros to declare repetitious parts of the class.

# include <boost/static_assert.hpp>

# include <boost/preprocessor/cat.hpp>
# include <boost/preprocessor/repetition/repeat.hpp>
# include <boost/preprocessor/repetition/enum.hpp>
# include <boost/preprocessor/repetition/enum_binary_params.hpp>
# include <boost/preprocessor/facilities/intercept.hpp>

These headers give us the tools to define the macro DEFINE_CLASS_ARRAY(..) which declares a class that acts like a fixed-size array except it has a constructor that takes as many args as the array is big. So an array with 57 elements will have a constructor function with 57 parameters.

# define PRINT_CLASS_ARRAY_INIT_ELEM_( Z, N, D)     \
    BOOST_PP_CAT( _, N) ( BOOST_PP_CAT( init, N) )

# define PRINT_CLASS_ARRAY_MEMBER_DECL_( Z, N, D)   \
    element_type BOOST_PP_CAT( _, N) ;

# define DEFINE_CLASS_ARRAY(                        \
    CLASS_NAME,                                     \
    ELEM_COUNT,                                     \
    ELEM_TYPE,                                      \
    PARAM_TYPE,                                     \
    DEFAULT_VALUE                                   \
)                                                   \
    class CLASS_NAME                                \
    {                                               \
      public:                                       \
        typedef ELEM_TYPE element_type;             \
                                                    \
        CLASS_NAME                                  \
        ( BOOST_PP_ENUM_BINARY_PARAMS( ELEM_COUNT   \
           , PARAM_TYPE init                        \
           , = DEFAULT_VALUE BOOST_PP_INTERCEPT     \
        ) )                                         \
          : BOOST_PP_ENUM( ELEM_COUNT               \
             , PRINT_CLASS_ARRAY_INIT_ELEM_         \
             , ~                                    \
            )                                       \
          { }                                       \
                                                    \
      public:                                       \
        BOOST_PP_REPEAT( ELEM_COUNT                 \
         , PRINT_CLASS_ARRAY_MEMBER_DECL_           \
         , ~                                        \
        )                                           \
    } /* end of macro DEFINE_CLASS_ARRAY(..)       */

Here is how you might use the above macro to define a class that acts like an array of 5 integers.

// Define the class
DEFINE_CLASS_ARRAY(
    five_ints_class, 5, int, const int&, 3);

// Same size as an array of 5 ints.
BOOST_STATIC_ASSERT(
    sizeof( five_ints_class) == sizeof( int[ 5 ]));

five_ints_class five_ints_0;
five_ints_class five_ints_1( 0);

five_ints_class five_ints_2( 0, 1);
five_ints_class five_ints_3( 0, 1, 2);

five_ints_class five_ints_4( 0, 1, 2, 3);
five_ints_class five_ints_5( 0, 1, 2, 3, 4);

// Too many args:
// five_ints_class five_ints_6( 0, 1, 2, 3, 4, 5);

// Cannot init with pod because it has a ctor:
// five_ints_class from_pod = { 1,2,3,4,5 };

This is just a quick hack and could be improved to (partly) mimic a standard container with additional typedefs, operators, methods, and associated iterators.

These array classes have no virtuals, no supertype, and no members besides the array elements. The resulting class is the same size as a raw array, and you could cautiously cast between the two types.

This defines classes with ctors that have default parameter values, but you could easily leave the defaults off. Or you could define the constructor as a template method and get rid of PARAM_TYPE from the macro. Or you could assume PARAM_TYPE is const ELEM_TYPE&.

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.

← Previous PageNext Page →