Boost logo

Boost :

From: Aleksey Gurtovoy (agurtovoy_at_[hidden])
Date: 2002-10-04 06:35:06


David B. Held wrote:
> > Of course, the fourth way would be this:
> >
> > 4)
> > // policies are still ordinary class templates, but with
> > // arbitrary number and order of template parameters
> >
> > template< typename T >
> > struct ownership_policy1
> > {
> > //...
> > };
> >
> > template< typename P1, typename T, typename P2 > // here
> > struct ownership_policy2
> > {
> > //...
> > };
> >
> > template< typename T >
> > struct conversion_policy2
> > {
> > //...
> > };
> >
> > // use MPL's lambda:
> >
> > typedef smart_ptr< my
> > , ownership_policy1<_>
> > , conversion_policy1<_>
> > , ...
> > > my_ptr;
> >
> > typedef smart_ptr< my
> > , ownership_policy2<param1,_,param2> // here!
> > , conversion_policy1<_>
> > , ...
> > > my_ptr2;
> >
> > The 'smart_static_cast' implementation still would be the
> > same as above (the sane one!).
>
> ...needs some explaining! I have a vague notion that MPL::lambda is
> "currying a metafunction"?

Yes, in the simplest case; basically you spell out a metafunction with some
arguments filled and some missing:

    typedef is_same<_,int> f;

and MPL allows you to convert it to metafunction-class - and after that you
can do whatever you want with it:

    template< typename Predicate > struct my
    {
        // turn 'Predicate' into a metafunction-class
        typedef typename mpl::lambda<Predicate>::type pred_;

        // now we can apply it!
        typedef mpl::apply<pred_,int>::type res_;
    };

BOOST_STATIC_ASSERT(my<f>::res_::value)

There are two things worth mentioning about this example:

 a) the ability of 'my' template to handle lambda expressions doesn't affect
its ability to work with ordinary, "plain" metafunction-classes:

    struct always_true
    {
        template< typename T > struct apply
            : mpl::true_c
        {
        };
    };

    // no problem!
    BOOST_STATIC_ASSERT(my<always_true>::res_::value)

 b) lambda expressions might be much much more complicated

    typedef or_<
          is_same<_,int>
        , equal< sizeof_<_>, sizeof_< add_pointer<_> >
> f2;

 and yet you don't need to change 'my' implementation in any way in order to
make it handle something like the above - it just works:

    // no problem as well
    BOOST_STATIC_ASSERT(my<f2>::res_::value)

> It is producing a unary metafunction class?

A metafunction class, yes. The arity depends on the arity of the original
lambda expression, obviously:

    typedef lambda< plus<_,_,_,_> >::type f4; // arity == 4
    typedef lambda< plus<_,int_c<5>,_,_> >::type f3; // arity == 3
    typedef lambda< is_same<_,_> >::type f2; // binary
    typedef lambda< is_same<void,_> >::type f1; // unary
    typedef lambda< ownership_policy1<_> >::type g1; // unary

> You lost me on how this gets implemented.

Oh, that's easy - here's how the definition of a smart pointer template that
accepts only plain metafunction-class parameters might look like:

    template <
          typename T
        , typename OwnershipPolicy
        , typename ConversionPolicy
        , typename CheckingPolicy
        , typename StoragePolicy
        , typename sp_ = typename apply<StoragePolicy,T>::type
>
    struct smart_ptr
        : sp_
        , apply<OwnershipPolicy, typename sp_::pointer_type>::type
        , apply<CheckingPolicy, typename sp_::pointer_type>::type
    {
        // ...
    };

    // usage
    struct ownership_policy1 // policies are metafunction-classes
    {
        template< typename T > struct apply
        {
            //...
        };
    };

    // ...
    typedef smart_ptr< my
        , ownership_policy1
        , conversion_policy1
        , ...
> my_ptr;

And here's an equivalent definition that accepts both meta-function classes
and lambda expressions:

    template< typename F, typename T > struct le_apply
    {
        typedef typename mpl::lambda<F>::type f_;
        typedef typename mpl::apply1<f_,T>::type type;
    };

    template <
          typename T
        , typename OwnershipPolicy
        , typename ConversionPolicy
        , typename CheckingPolicy
        , typename StoragePolicy
        , typename sp_ = typename le_apply<StoragePolicy,T>::type
>
    struct smart_ptr
        : sp_
        , le_apply<OwnershipPolicy, typename sp_::pointer_type>::type
        , le_apply<CheckingPolicy, typename sp_::pointer_type>::type
    {
        // ...
    };

    // usage

    // policies can have form of both ordinary class templates...
    template< typename T >
    struct ownership_policy1
    {
        //...
    };

    // ...and metafunction-classes
    struct conversion_policy1
    {
        template< typename T > struct apply
        {
            //...
        };
    };

    // ...
    typedef smart_ptr< my
        , ownership_policy1<_>
        , conversion_policy1
        , ...
> my_ptr;

    // and they can take pretty sophisticated forms as well
    typedef smart_ptr< my
        , ownership_policy1< add_const< add_pointer<_> > >
        , conversion_policy1
        , ...
> my_const_ptr;

> In fact, that code looks like pure magic to me...

It's because it is pure magic :).

> I have no idea how to go from what you wrote to
> something my compiler will recognize. ;)

If we are talking about Borland, my local copy of the library passes all but
3 tests (of 54), so don't worry about that :).

> Before I go converting everything to canonical template form or whatever,
> I could really use a bit of a tutorial on what's going on above.

Well, I hope the above clarifies things a little bit.

Aleksey


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