Boost logo

Boost :

Subject: Re: [boost] Local and Phoenix idiosyncrasy
From: Lorenzo Caminiti (lorcaminiti_at_[hidden])
Date: 2011-11-26 07:28:43


On Thu, Nov 24, 2011 at 1:14 PM, Thomas Heller
<thom.heller_at_[hidden]> wrote:
> Hi list,
>
> Can we please put an end to this insane idiosyncrasy going on here lately?
> It doesn't lead to anywhere!

Sorry, I didn't reply sooner to this thread but I needed to study
Phoenix more before I could reply (and I've been pretty busy with the
review replies in the last week :) ). Please note that there's still a
lot I need to learn about Phoenix so help me here (if I knew that
Phoenix/Local was going to be such a big topic, I would have studied
Phoenix _before_ the review but from the discussion we had ~1 year ago
I incorrectly concluded this issue was settled).

> Ok, we get it ... people want to write local functions in the usual way.
>
> Here is what I have to propose: Why not combine Boost.Local and
> Boost.Phoenix and keep everybody happy?
> If the macros provided by Boost.Local would emit some instance of a
> phoenix::function.

With the following, you can create a Phoenix function from a local
function simply by doing:

// localfunc defined at local scope...
boost::phoenix::function<BOOST_LOCAL_TYPEOF(localfunc)> phoenixfunc =
localfunc; // (1)

For example:

#include <vector>
#include <algorithm>
#include <iostream>
#include <boost/local/function.hpp>
#include <boost/phoenix/core.hpp>
#include <boost/phoenix/function.hpp>

// TODO: Is there a better way to do this? Ideally, I'd want the result type to
// be the X template parameter without introducing the extra R...
template<typename R>
struct global_add {
    typedef R result_type;

    template<typename X, typename Offset>
    R operator()(X x, Offset offset) const {
        X total = x + offset;
        std::cout << total << std::endl;
        return total;
    }
};

int main() {
    using boost::phoenix::arg_names::arg1;

    std::vector<int> v(3);
    v[0] = 1; v[1] = 2; v[2] = 3;
    int offset = 10;

    // Phoenix function defined globally:
    boost::phoenix::function< global_add<int> > pg_add;
    std::for_each(v.begin(), v.end(), pg_add(arg1, offset));

    // Phoenix function defined locally:
    int BOOST_LOCAL_FUNCTION_PARAMS(int x, bind offset) {
        int total = x + offset;
        std::cout << total << std::endl;
        return total;
    } BOOST_LOCAL_FUNCTION_NAME(local_add)

    boost::phoenix::function<BOOST_LOCAL_TYPEOF(local_add)> pl_add = local_add;
    std::for_each(v.begin(), v.end(), pl_add(arg1));

    return 0;
}

Implementation: This only required me to typedef result_type into the
functor used by local (expanded by PARAMS) and to do another typdef at
local scope to expose the type for
BOOST_LOCAL_TYPE(local_function_name) (expanded by NAME).

If users need Phoenix function benefits (lazy evaluation, etc) but
they want to define the Phoenix function locally and using statement
syntax, then they create the Phoenix function from the local function.
If they don't need the Phoenix function benefits, then they just use
the local function. This way, the relationship between Local and
Phoenix is very clear, I add the needed typedefs to the Local
implementation, line (1) is added to the Phoenix docs, and that's all
:))

Would this be useful?

> It would be great if this could be done completely
> polymorphic, maybe some tricks with boost::any?

Note that in the example above the Phoenix global function is
polymorphic (uses the template parameter X, even if I couldn't get
around also using R (I didn't try hard)...) while the Phoenix local
function is not (uses int). Of course a local function can use
boost::any:

#include <boost/local/function.hpp>
#include <boost/any.hpp>
#include <vector>
#include <string>

int main () {
    std::vector<boost::any> values;
    void BOOST_LOCAL_FUNCTION_PARAMS(boost::any const& value, bind& values) {
        values.push_back(value);
    } BOOST_LOCAL_FUNCTION_NAME(append)

    std::vector<int> i(3);
    i[0] = 1; i[1] = 2; i[2] = 3;
    std::for_each(i.begin(), i.end(), append);

    std::vector<std::string> s(3);
    s[0] = "abc"; s[1] = "uvw"; s[2] = "xyz";
    std::for_each(s.begin(), s.end(), append);

    return 0;
}

I don't see a way to improve this (i.e., going from the generic
boost::any type to a real polymorphic type) without template
parameters within local types :( What do you think?

> That way we gain these advantages:
> 1) We can get rid of the need to capture variables in scope by the macro.
> 2) People who don't want to put their functions in global/namespace/class
> scope and are afraid of writing a phoenix expression don't have to
> 3) The resulting lazy function will be composable in the usual phoenix way
> and will be far more useful!
> 4) We can put an end to this useless Phoenix vs. Local discussion

Thanks in advance.
--Lorenzo


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