Boost logo

Boost :

Subject: Re: [boost] [interest] Type Aspects and compile-time constraints
From: Beren Minor (beren.minor+boost_at_[hidden])
Date: 2012-02-08 18:58:18


> Looks to me like the traits idiom (e.g., std::iterator_traits). Perhaps a
> concrete example will illustrate your point better?

It's close to what std::iterator_traits defines but generalizes it.
Let's get more practical and take my "Numeric" aspect I was talking
about above.

I want to describe number related types over a given number of
properties. Let say that signness, integralness and width are
properties that matter for numeric types.
These aspect axises could be defined in a similar way by givin them an
"Aspect" name, and a given set of values:

META_ASPECT_DECLARE(sign, (signed_)(unsigned_)(any))
META_ASPECT_DECLARE(integral, (floating)(fixed)(integral)(boolean)(any))
META_ASPECT_DECLARE(width, (one_bit)(two_bits)(three_bits)
                           [...] (eight_bytes)(any))

This defines three aspects axises, sign, integral and width which
could take the given values.
This is a slightly simplified version of things I'm actually using.
The detail of the macros isn't very relevant.

Now let's define the aspect of a numerical type:

template< typename Type, typename Sign, typename Integral, typename Width >
struct numeric_type_aspect {
  typedef Type type;
  typedef Sign sign;
  typedef Integral integral;
  typedef Width width;
};

#define DECLARE_TYPE(type_m, sign_m, integral_m, width_m) \
  template< > struct numeric_type< type::type_m > { \
    typedef numeric_type_aspect< type::type_m, \
                                 sign::sign_m, \
                                 integral::integral_m, \
                                 width::width_m > aspect; \
  }; \
  typedef numeric_type< type::type_m > type_m;

        DECLARE_TYPE(signed_, signed_, any, any)
        DECLARE_TYPE(unsigned_, unsigned_, any, any)

        DECLARE_TYPE(integral_, any, integral, any)
        DECLARE_TYPE(signed_integral, signed_, integral, any)

                [...]

        DECLARE_TYPE(uint1_, unsigned_, integral, one_bit)
        DECLARE_TYPE(uint2_, unsigned_, integral, two_bits)
        DECLARE_TYPE(uint4_, unsigned_, integral, four_bits)
        DECLARE_TYPE(float_least10_, signed_, floating, eleven_bits)

#undef DECLARE_TYPE

This is purely abstract types for my internal constraint checking but
this could be done more the same way when describing any type.
Here I've defined a full list of numeric types, more or less defined
(signed_ is a vaguely defined signed_ type, whereas uint4_ is a fully
defined unsigned integral type on 4bits) that are described on a given
set of axises (three plus one which is the actual type itself).

The fact that the type itself is a description axis allows me to use
it to describe the numeric aspect of some more general data type.
For example the way OpenGL accepts data to load from host memory to
the GPU textures can be defined using a numeric aspect, as long as
with some other:

META_ASPECT_DECLARE(packing, (one_in_one)(two_in_one)
                             (three_in_one)(four_in_one))
META_ASPECT_DECLARE(order, (forward)(reverse))

template< typename Type, typename Numeric, typename Packing, typename Order >
struct data_type_aspect {
    typedef Type type;
    typedef Numeric numeric;
    typedef Packing packing;
    typedef Order order;
};

#define DECLARE_TYPE(type_m, numeric_m, packing_m, order_m) \
  template< > struct data_type< type::type_m > { \
    typedef data_type_aspect< type::type_m, \
                              fcmn::numeric::numeric_m, \
                              fcmn::packing::packing_m, \
                              fcmn::order::order_m > aspect; \
  }; \
  typedef data_type< type::type_m > type_m;

        DECLARE_TYPE(gl_unsigned_byte, uint8_, one_in_one, forward)
        DECLARE_TYPE(gl_byte, int8_, one_in_one, forward)
        DECLARE_TYPE(gl_unsigned_short, uint16_, one_in_one, forward)
        DECLARE_TYPE(gl_short, int16_, one_in_one, forward)

                [...]

        DECLARE_TYPE(gl_unsigned_int_24_8, ufloat32_, two_in_one, forward)
        DECLARE_TYPE(gl_unsigned_int_10f_11f_11f_rev, ufloat32_,
                     three_in_one, reverse)
        DECLARE_TYPE(gl_unsigned_int_5_9_9_9_rev, ufloat32_,
                     three_in_one, reverse)
        DECLARE_TYPE(gl_float_32_unsigned_int_24_8_rev, float32_stride32_,
                     two_in_one, reverse)

#undef DECLARE_TYPE

The numeric aspect of every data type is one of the numeric type
defined above, which has all its properties well defined. Then, the
packing aspect can describe how colors components are packed in the
source data (we can have a set of integers on 32bits but do we have
one, two, three or four components packed in it?), then the order
aspect describes how these components are ordered.

Then constraints can be written by querying these aspects (which is
done in a generalized way, and using boilerplate code generated by the
macros) and using MPL to generate boolean conditions:

template< typename DataType, typename GroupType >
struct integral_check {
  typedef bm::and_< fnum::integral::is_integral<
                      typename fcmn::get_numeric< GroupType >::type
>,
                    fnum::integral::is_not_integral<
                      typename fcmn::get_numeric< DataType >::type
> > group_is_integral_but_data_is_not;
  typedef bm::not_< group_is_integral_but_data_is_not > type;

  static_assert(type::value, "GroupType is not compatible with DataType");
  static_assert(type::value, "");
  static_assert(type::value, " [3.7.2 Transfer of Pixel Rectangles]");
  static_assert(type::value, " [3.9.3 Texture Image Specification]");
  static_assert(bm::not_< group_is_integral_but_data_is_not >::value,
                " - GroupType is integral but DataType is not.");
};

template< typename DataType, typename GroupType >
struct component_check {
  typedef bm::and_< fcmn::component::is_depth_stencil< GroupType >,
                    fcmn::packing::is_not_two_in_one< DataType >
> group_is_depth_stencil_but_data_is_not_two_packed;
  typedef bm::not_< group_is_depth_stencil_but_data_is_not_two_packed >
            type;

  static_assert(type::value, "GroupType is not compatible with DataType");
  static_assert(type::value, "");
  static_assert(type::value, " [3.7.2 Transfer of Pixel Rectangles]");
  static_assert(type::value, " [3.9.3 Texture Image Specification]");
  static_assert(bm::not_<
                  group_is_depth_stencil_but_data_is_not_two_packed
>::value,
                " - GroupType is depth_stencil but DataType is not
two_in_one packed.");
};

--
Beren Minor

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk