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&.

Comments

Leave a Reply