Boost logo

Boost :

From: David Abrahams (david.abrahams_at_[hidden])
Date: 2001-12-11 14:21:13


----- Original Message -----
From: "David A. Greene" <greened_at_[hidden]>

> I've snipped some non-obvious stuff. I really dislike the
> "quoted" metafunctions. I realize they're needed to satisfy
> broken compilers, but it's such a shame that much of the
> potential elegance of metaprogramming is lost (at least to
> this novice).

I think the "quoted" name hurts the understandability of the idiom for
anyone but a lisp programmer. There are technical reasons apart from broken
compilers to prefer this idiom, but I'll let Aleksey address them.

> I also dislike the fact that lots of little classes have to
> be declared just to force GenScatterHierarchy and Fields into
> for-loop implementations. It's really not obvious why "reverse
> inherit" is needed.
>
> > template <class TList, class QuotedMF>
> > struct GenScatterHierarchy {
> >
> > public:
> > typedef boost::mpl::for_each<
> > TList // for each type in TList
> > , EmptyType // begin with state EmptyType
> > , boost::mpl::compose_f_gx_hy<
> > ReverseInheritTwoQuoted
> > , boost::mpl::identity<boost::mpl::_1>
> > , QuotedMF>
> > // apply binary metafunctor mapping <oldState, aType>
> > // to InheritTwo<unary_function<Unit,aType>::type, oldState>
> > >::state type;
> > };
>
> By this point I was almost totally dumbfounded. That's a lot of
> ugly code to do a conceptually very simple thing. Andrei is right.
> Template metaprogramming is more naturally expressed using recursion
> because templates define a functional language. I'm reminded of the
> ugly transformations from recursion to iteration, or explicity
> coding a parser value stack rather than using inherited and
> synthesized attributes. Yes, it's possible, but why do it when it
> is often much simpler to let things "fall out" naturally? Less
> code, fewer bugs.

I think you're drawing conclusions from things unrelated to the conclusions
themselves. Consider a similar runtime construct using stl, written in a
similar style:

template <class T, class F, class II, class II>
T gen_scatter_hierarchy(II start, II finish, F f)
{
    return std::accumulate(
        start
        , finish
        , boost::compose_f_gx_hy(
              ReverseInheritTwoQuoted()
            , boost::identity<value_type>()
            , m_f));
};

People have the same complaints about the STL. We all know that the use of
lots of composers and binders can make code hard to read, and many of us
wouldn't write that way for runtime code. Let's see if we can rewrite Mat's
example more simply, if in more characters:

// warning, untested

   // The product of GenScatterHierarchy will always be one of these,
   // with leaves being the classes in the type list.
   template <class L, class R>
   struct InheritTwo : public L , public R
   {
     typedef L Left;
     typedef R Right;
   };

// F is the transformation that will be bound into
// this function object and applied to each element;
// think of this as a parametrized function adaptor.
template <class F>
struct inherit_transformed
{
    // This is the function object's operator()()
    template <class Product, class Element>
    struct apply
    {
        // first transform the type
        typedef typename F::template apply<Element>::type transformed;

        // This is the return value: we inherit from the
        // current element and everything accumulated so far
        typedef InheritTwo<transformed_type, Product> type;
    };
};

// Now we can define GenScatterHierarchy

   template <class TList, class Transformation>
   struct GenScatterHierarchy {
        typedef boost::mpl::for_each<
            TList, EmptyType, inherit_transformed<Transformation>
>::state type;
   };

I find the analogy between the use of the STL and the use of MPL makes
things much easier. For example, you could imagine a run-time analogue to
inherit_transformed, which could be used with std::accumulate:

template <class F>
struct sum_of_f_of
{
    sum_of_f_of(F f = F()) : m_f(f) {}

    double operator()(double x, double y)
    {
        double transformed = m_f(y);
        return x + transformed;
    }
}

Composers and binders become really useful when you have lots of little
metafunctions lying around for doing important jobs in your domain. At that
point, you want to be able to re-use and combine them. Of course, a
compile-time lambda facility (which has already been started in MPL) could
be much nicer.

-Dave


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