Boost logo

Glas :

[glas] Operator overloading

From: Andreas Pokorny (andreas.pokorny_at_[hidden])
Date: 2006-03-07 18:44:14


Hi,
I just noticed the Operator overloading [1] document on glas.sf.net.
As part of my thesis work I implemented a domain specific language
framework inspired by xpressives boost::proto templates. It extends the
latter by a domain_tag, and a rule system, and allows to attach attributes
to the expression tree.

I believe you might find the library design intersting.

A user of the framework would define a domain_tag, like
linear_algebra_domain_tag, and define rules for operator overloading and
frontend functions. The rules are structure templates partially
specialized by the domain tag, a type tag discribing the operation
and the operand types. Currently only binary and unary operations are
handled.

E.g. the rule for elementwise operators like + and - which require the
parameters to have the same extends could look like that:

struct linear_algebra_domain_tag{};

template<typename OpTag,typename LT,typename RT>
struct rule<linear_algebra_domain_tag,OpTag,LT,RT,
  typename enable_if<
    mpl::and_<
      numeric::prototype::detail::requires_equal_dimension<OpTag>
      // ^ yields true for addition substracion ...
      , numeric::prototype::detail::have_equal_dimension<LT,RT>
      // ^ could check compile time extends, and attributes carring
      // meta information like whether the operand is column vector or a scalar
>
>::type >
    :
   defined ///< Required to terminate the search for a domain
                 /// since the framework allows to extend an existing domain
   , mpl::true_ ///< Tells the framework, that the operation is allowed
{

  // inside the rule body, the user has to specfiy the resulf of
  // the operation, in terms of an attributed expression tree,

  // e.g. this rule yields a binary expression containing the
  // two operands and a fusion attribute map,

  typedef unification::binary_expression<
    LT
    , RT
    , typename fusion::result_of::as_map<
        typename fusion::result_of::push_front<
          typename unification::result_of::construct<OpTag,LT,RT>::type
          // ^ explained below
          , ct_pair<unification::domain_tag,linear_algebra_domain_tag>
>::type
          // ^ attaches the domain tag type, it is possible to switch
          // into a different domain, if required.
>::type
> result_type;

  // a static init function has to be provdid creating the resulf of the
  // opration.

  static result_type init( LT const& left, RT const& right )
  {
    // additional runtime checking should happen here
    return result_type(
             left
             , right
             , as_map(
               fusion::push_front(
                 unification::construct<OpTag>( left, right )
                   , ct_pair<unification::domain_tag,linear_algebra_domain_tag>()
                   )
               )
             );
  };
};

I would like to explain attribute map creation in further details.
The code above uses a fusion-2 map to carry all user defined expression
attributes. Every attribute is a pair consisting of a key type tag, and
a value type. The user may use fusion::pair and ct_pair. The former
stores only an instance of the value type, and the latter does not create
any instance of the types. So the user may decide wether the information
is compile time only, or maybe both runtime and compile time.

To simplify the creation of the attribute map inside of a rule the user
may use the construct meta function. Which combines the attribute maps
of the two operands. Inside of construct the local attributes get filtered.
Then the attributes are sorted into three groups, attributes which only
occur in either of the two operands, and attributes which occur in both.
Each group of attributes, with the attached value types are then passed
to different user defined meta functions, which are supposed to update
the attribute. The user also has to specifiy whether attributes are local
or non local using a meta function.

The rule system also handles the automatic adaption of ``foreign'' types.
E.g. one could use a boost::multi_array<T,2> as matrix provided that
is_scalar<T> yields true. Rules to adapt can be specified similarly with
respect to the domain tag and the operation it occurs in.

By using SFINAE inside the operators and functions the user will never see
any "instantiated by" in their error messages as soon as illegal expressions are
detected, instead the compiler should only state that there is no matching
operand or function defined.

So far I only implemented the framework as a sort of experiment, the main
thesis code does not use it, yet. Instead it uses a striped down version
of the rule system without the domain tag, and without the fusion attribute
maps. The switch to the rule system made the operators and frontend functions
trivial to implement. I even fixed several bugs in the expression checking for
the operators, just by switching to the external rules.
As soon as I finish my thesis(3 weeks to go) I will polish and document
the library, and add further features like the so called `compilers',
which proto provides.
So feature wishes, and opinions are very welcome.

Regards
Andreas Pokorny

[1] http://glas.sourceforge.net/doc/design/operator_overloading.html