Boost logo

Boost Users :

From: David Abrahams (dave_at_[hidden])
Date: 2003-05-20 10:56:46


Tarjei Knapstad <tarjeik_at_[hidden]> writes:

> On Mon, 2003-05-19 at 21:34, David Abrahams wrote:
>> Tarjei Knapstad <tarjeik_at_[hidden]> writes:
>>
>> > I encountered the following problem today which I've reduced down to a
>> > simple example. Basically what I want to do is recurse through a
>> > typelist (mpl::vector) popping an element each time and end recursion by
>> > using apply_if with mpl::empty<List> as the condition. I've made a
>> > simple Reverse class template to illustrate the problem. (The Reverse
>> > class is of course intended to reverse the contents of a typelist....)
>> >
>> > --------------------- begin code ---------------------
>> >
>> > #include <boost/mpl/front.hpp>
>> > #include <boost/mpl/push_back.hpp>
>> > #include <boost/mpl/pop_front.hpp>
>> > #include <boost/mpl/apply_if.hpp>
>> > #include <boost/mpl/empty.hpp>
>> > #include <boost/mpl/size.hpp>
>> > #include <boost/mpl/vector.hpp>
>> >
>> > using namespace boost::mpl;
>> >
>> > template <typename List>
>> > struct Reverse
>> > {
>> > typedef typename front<List>::type FrontElem;
>> > typedef typename pop_front<List>::type Rest;
>> > typedef typename apply_if<
>> > empty<Rest>,
>> > vector<FrontElem>,
>> > typename push_back<typename Reverse<Rest>::Type,
>> > FrontElem>::type
>> > >::type Type;
>> > };
>> >
>> > typedef vector<int,int,long> List;
>> > typedef Reverse<List>::Type RevList;
>> >

<snip>

>>and it's easy to see that pop_front<List>::type is
>> evaluated whether List is empty or not.
>>
> ...is it?

Sure! What happens when you try to Reverse an empty sequence?

> I thought apply_if<> only instantiated either the 'true' or
> 'false' statement depending on the condition.

Yes, but it doesn't inhibit evaluation *lexically*. Whenever you
write ::type, you get a non-lazy evaluation. In fact, your apply_if
is not delaying anything, since you greedily evaluate the 2nd
argument, and the first argument is already the right type. You'd get
the first argument correctly because

       vector<T1, T2, ... TN>::type == vector<T1, T2, ... TN>

And for the same reasons, the 2nd argument to apply_if has the right
type but is evaluated early. Even if you stripped the outer
typename...::type and wrote:

     typedef typename apply_if<
         empty<Rest>
       , vector<FrontElem>
       , push_back<
             typename Reverse<Rest>::Type
           , FrontElem
>
>::type Type;

you'd still be in trouble because Reverse is being evaluated
greedily. The code above translates to something like (runtime):

     return ( // select a function object
          Rest.empty()
        ? boost::bind(identity, seq<type>(1, FrontElem))
        : boost::bind(push_back, reverse(Rest), FrontElem)
        )
        (); // now invoke it

If you really want to do it that way, break the thing you want to
delay out into a separate metafunction:

    template <class Seq, class E>
    struct append_reversed
      : push_back<typename Reverse<Seq>::type, E>
    {
    };

    ...

     typedef typename apply_if<
         empty<Rest>
       , vector<FrontElem>
       , append_reversed<Rest,FrontElem>
>::type Type;

or, apply a lambda expression explicitly:

     typedef typename apply_if<
         empty<Rest>
       , vector<FrontElem>
       , apply<
             lambda<push_back<Reverse<_1>, FrontElem> >
           , Rest
>
>::type Type;

>> Some things to note:
>>
>> 1. If your algorithm is doing repeated pop_fronts, you probably
>> want a list: since each successive list is a sublist of the
>> previous one, the new list doesn't cost any instantiations
>>
>> 2. Barring that, use an iterator_range to avoid instantiating a
>> new vector at each iteration
>>
> Thanks, will keep in mind. In fact it looks like I can more or less
> adopt my STL mindset to the MPL which I guess is half the point :)
> (barring the pop_back stuff ;) )

Oh, and please, do yourself a favor and follow the MPL metafunction
protocol! That means your result is called "::type", not "::Type".

>> 3. The easy way to do this is
>>
>> mpl::fold<
>> List
>> , mpl::push_front<>
>> , typename mpl::clear<List>::type>::type
>> >
>>
>> I believe.
>>
> Thanks a lot, this works great! (except that the push_front and clear
> arguments need to be switched around)
>
> Thanks once again for your help Dave,

Sure thing.

-- 
Dave Abrahams
Boost Consulting
www.boost-consulting.com

Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net