Sophie

Sophie

distrib > Mandriva > 9.1 > i586 > by-pkgid > b9ba69a436161613d8fb030c8c726a8e > files > 370

spirit-1.5.1-2mdk.noarch.rpm

Hi,

---------------------

Thanks for the feedback. I read them all with interest. I still don't
have net access yet so pardon me if I reply scarcely. Anyway, I read
some interesting wish lists. What I need to point out is that phoenix
is **sooo** extensible that most of these wish list items are trivial
to implement. And so...

To give you another glimpse into the power of phoenix when it comes to
extensibility, I hacked in less than 1 hour code that implements
*TRUE* local variables in the framework. Note: Not to be confused with
closures. No intrusive code was written at all and the original
framework remains as is.

(see sample7.cpp)

---------------------

First let me start with an appetizer:

    for_each(c.begin(), c.end(),
        locals<int, char const*>(0, "...That's all\n")
        [
            for_(loc1 = 0, loc1 < arg1, ++loc1)
            [
                cout << loc1 << ", "
            ],
            cout << loc2
        ]
    );

loc1 is a true local variable. Predefined are loc1..locN.
Prior to evaluation,

    locals<int, char const*>(0, "...Bye Bye\n")

creates an int local variable initialized to 0 and another char const*
local variable initialized to "...That's all\n". These variables can
be any type and as much as N (a predefined maximum) local variables
can be declared. Examples:

    locals<int, double>(0, 3.6)
    locals<char, double, std::string>('j', 3.6, "Hello World")

The initializer can be default constructed:

    locals<int, double>()

The types can be omitted:

    locals(0, 3.6)
    locals('j', 3.6, "Hello World")

The types here are inferred from the parameters. Take note though that
the second is equivalent to locals<char, double, char const[N]>
instead of locals<char, double, std::string>. Of course you can write:

    locals('j', 3.6, std::string("Hello World"))

To make it explicit.

The local variable's scope is the expression or statements inside the
brackets following it: i.e. some_expression in:

    locals<types>(init)[some_expression]

loc1..locN correspond to the Nth local variable declared in
locals<T0...TN>.

We've seen the for_ before. Obviously loc1 = 0, the for_ initializer
is redundant because it is initialized already in the locals
initialization. Anyway... this is just for demonstration. Perhaps
it's better to use a while_(blah)[blah_blah].

---------------------
Now on to some advanced phoenix coding:

*** Warning, understanding the following code requires some knowledge
of the framework as written in the "inside phoenix" of the docs. ***
*** Warning, also requires mastery of templates ***

First, we need a special tuple duo. It *is a* tuple like the one we
see in TupleT in any actor base class' eval member function. Let's
call it local_tuple. local_tuple should look and feel the same as a
tupled-args, that's why it is derived from TupleArgsT. It has an added
member though, locs. The member locs is another tuple where the local
variables will be stored. locs is mutable to allow read-write access
to our locals regardless of local_tuple's constness (The eval member
function accepts it as a const argument).

    template <typename TupleArgsT, typename TupleLocsT>
    struct local_tuple : public TupleArgsT {

        typedef TupleLocsT local_vars_t;

        local_tuple(TupleArgsT const& args, TupleLocsT const& locs_)
        :   TupleArgsT(args), locs(locs_) {}

        mutable TupleLocsT locs;
    };

********************

Here, we will write our primitive local_var class. It looks so
curiously like the argument class in primitives.hpp. local_var
provides access to the Nth local variable packed in the tuple duo
local_tuple above. Note that the member function eval expects a
local_tuple argument. Otherwise the expression 'tuple.locs' will fail
(compile-time error). local_var primitives only work within the
context of a locals_composite (see below).

    template <int N>
    struct local_var {

        template <typename TupleT>
        struct result {

            typedef typename local_var_result<N, TupleT>::type type;
        };

        template <typename TupleT>
        typename local_var_result<N, TupleT>::type
        eval(TupleT const& tuple) const
        {
            return tuple.locs[tuple_index<N>()];
        }
    };

local_var_result is a return type computer. Given a constant integer N
and a tuple, get the Nth local variable type. If TupleT is not really
a local_tuple, we just return nil_t. Otherwise we get the Nth local
variable type.

    template <int N, typename TupleT>
    struct local_var_result {

        typedef nil_t type;
    };

This one above, is for the generic case. Our local_var works
only with local_tuple types so the one above just returns nil_t.

    template <int N, typename TupleArgsT, typename TupleLocsT>
    struct local_var_result<N, local_tuple<TupleArgsT, TupleLocsT> > {

        typedef typename tuple_element<N, TupleLocsT>::type& type;
    };

This one is a specialization for local_tuple types. Here, we get the
Nth tuple_element of local_tuple::local_vars_t (or TupleLocsT).

Let us also provide some predefined local_var actors:

    actor<local_var<0> > const loc1 = local_var<0>();
    actor<local_var<1> > const loc2 = local_var<1>();
    actor<local_var<2> > const loc3 = local_var<2>();
    actor<local_var<3> > const loc4 = local_var<3>();
    actor<local_var<4> > const loc5 = local_var<4>();

********************

Now let's write a composite. Let's call it locals_composite. This
class encapsulates an actor and some local variable initializers
packed in a tuple.

    template <typename ActorT, typename LocsT>
    struct locals_composite {

        typedef locals_composite<ActorT, LocsT> self_t;

        template <typename TupleT>
        struct result { typedef typename actor_result<ActorT, TupleT>::type type; };

        locals_composite(ActorT const& actor_, LocsT const& locals_)
        :   actor(actor_), locals(locals_) {}

        template <typename TupleT>
        typename actor_result<ActorT, TupleT>::type
        eval(TupleT const& args) const
        {
            return actor.eval(local_tuple<TupleT, LocsT>(args, locals));
        }

        ActorT actor;
        LocsT locals;
    };

Let's take this one at a time:

    typedef locals_composite<ActorT, LocsT> self_t;

Our self type.

    template <typename TupleT>
    struct result { typedef typename actor_result<ActorT, TupleT>::type type; };

The result of our eval function is just the actor_result of our
embedded actor.

    locals_composite(ActorT const& actor_, LocsT const& locals_)
    :   actor(actor_), locals(locals_) {}

our constructor. Just stuff the input to our member vars.

    template <typename TupleT>
    typename actor_result<ActorT, TupleT>::type
    eval(TupleT const& args) const
    {
        actor.eval(local_tuple<TupleT, LocsT>(args, locals));
    }

Here is our eval member function. locals_composite is just like a
proxy and delegates the actual evaluation to the actor. The actor does
the actual work. In the eval member function, before invoking the
embedded actor's eval member function, we first stuff an instance of
our locals and bundle both 'args' and 'locals' in a local_tuple. This
local_tuple instance is created in the stack initializing it with our
locals member. We then pass this local_tuple instance as an argument
to the actor's eval member function.

********************

Of course this wouldn't be complete if we didn't supply a generator
for our composite. We have a 2-step generator. Here is the second.
The first will be given last (;-)...

    template <typename LocsT>
    struct locals_gen {

        locals_gen(LocsT const& locals_)
        :   locals(locals_) {}

        template <typename ActorT>
        actor<locals_composite<typename as_actor<ActorT>::type, LocsT> >
        operator[](ActorT const& actor)
        {
            return locals_composite<typename as_actor<ActorT>::type, LocsT>
                (as_actor<ActorT>::convert(actor), locals);
        }

        LocsT locals;
    };

One at a time:

    locals_gen(LocsT const& locals_)
    :   locals(locals_) {}

At construction time, this class is given some local var-initializers
packed in a tuple. We just store this for later.

    actor<locals_composite<typename as_actor<ActorT>::type, LocsT> >
    operator[](ActorT const& actor)

The operator[] of this class creates the actual locals_composite given
an actor. This is responsible for the construct locals<types>[actor].
As usual, we convert the input to a valid actor in:

    as_actor<ActorT>::type

and its corresponding:

    as_actor<ActorT>::convert(actor)

Finally, we must provide our front end generators. These generators
are overloaded for 1..N local variables. Only 1-local and 2-local
versions are provided. The others are straightforward variations.

    template <typename T0>
    inline locals_gen<tuple<T0> >
    locals(T0 const& _0)
    {
        typedef tuple<T0> tuple_t;
        return locals_gen<tuple_t>(tuple_t(_0));
    }

    template <typename T0, typename T1>
    inline locals_gen<tuple<T0, T1> >
    locals(T0 const& _0, T1 const& _1)
    {
        typedef tuple<T0, T1> tuple_t;
        return locals_gen<tuple_t>(tuple_t(_0, _1));
    }

So, when we see the expression:

    locals<int>(0)
    [
        blah
    ]

Here's what happens:

    template <typename T0>
    inline locals_gen<tuple<T0> >
    locals(T0 const& _0)

is called first. locals<int>(0) generates another generator
locals_gen. locals_gen has a member operator[] that returns an
actor<locals_composite<..blah..> >:

    template <typename ActorT>
    actor<locals_composite<typename as_actor<ActorT>::type, LocsT> >
    operator[](ActorT const& actor)

Thus, the final result is an actor<locals_composite<..blah..> >.

This concludes the front-end. Now on to the back-end. STL's for_each
calls the actor<locals_composite> passing in the container's element
as arg1. Doing so, locals_composite's eval is called:

    eval(TupleT const& args) const
    {
        actor.eval(local_tuple<TupleT, LocsT>(args, locals));
    }

This eval member function bundles the args passed in plus an instance
of our local variables 'locals'. This is then passed to its actor. The
actor in our example just so happens to be a for_composite (see
statement.hpp). for_composite does its usual stuff. At some point, the
primitive 'loc1' is evaluated. 'loc1' extracts the actual local
variable from the tupled arguments plus locals passed in by the
locals_composite.

---------------------
Now, it took me much longer to write this than the code itself.
Have fun!

Some more talk :-)

** cout_, endl_ etc. can indeed be made available to all platforms.

** I am aware of the importance of switch_ and case_ for completeness.
   I am just not sure yet on how the final syntax would be. As you can
   see above, implementation is easy. Designing the interface is difficult.
   BTW you might see that the above code is useful. It is. I am just not
   sure yet if it is the best syntax for locals.

** Adapting closures is as trivial as above. I am not sure yet whether
   to:
   1) migrate spirit-closures to phoenix (where it really should be)
   2) adapt spirit-closures to phoenix (temporarily)

** There's ***sooo*** much you can do with phoenix. It's really very
   tempting to play with it and extend it to extremes. I'd rather resist
   the temptation.
   1) I want to go back to Spirit coding :-)
   2) The architecture is open like STL. Purist as I am, I prefer packaging
   an extremely cohesive set of features, no more, no less. Keep it as
   simple as possible, but not simpler :-) People may then contribute
   add-ons as they wish. These add-ons will be in the form of external
   modules. Not unless a certain feature is simple and useful enough
   to many, then, it may migrate to the core. In fact, I'm not even sure
   if the special_ops.hpp (std adapters) should be there at all.

** I read Martijn's qsort challenge with interest. This is certainly
   possible. However, I doubt its usefulness other than for
   demonstration. Remember that lazy-operators and lazy statements
   have some overhead. The operators-statements part of phoenix,
   although quite interesting, should be used sparingly. I am not sure
   yet how much overhead there is. I invite people to do some
   benchmarks for lazy-statements/operators vs. lazy-functions. I
   certainly need that in the docs. There are surely some space/time
   overhead. I am not sure though how the compiler optimizer will
   handle that. (BTW, hey, LL has the same overhead).

** The main center-stage, IMO, should be the rank-2 polymorphic lazy
   functions. In conjunction with well-placed lazy-ops and lazy-
   statements, lazy-functions are very powerful workhorses. An
   interesting extension might be to write lazy-function versions of
   the STL algorithms that just forwards to the STL stuff. Now that
   would be truly useful! (and trivial :) Example:

        struct for_each_impl {

            template <typename BeginT, typename EndT, typename DoT>
            struct result { typedef DoT type; };

            template <typename IterT, typename DoT>
            DoT operator()(IterT first, IterT last, DoT do_) const
            { return std::for_each(first, last, do_); }
        };

    function<for_each_impl> for_each_;

Regards,
--Joel

PS> As for the other messages, pardon me. I will try to catch
up as soon as I get online again.

Cheers....