alfC
alfC

Reputation: 16242

Is it possible to make Phoenix a notch less greedy about binary operators?

I wanted to have a class category that takes (unevaluated) Phoenix expressions through a binary operator. Basically the idea is that the class processes the expressions and, for example, prints the expression to screen.

The problem is that Phoenix overloads all the binary operators and unless there is an exact match the Phoenix (lazy) operator is preferred. Is it possible to make the Phoenix just a bit less greedy about hijacking operators?

Example code:

#include<boost/phoenix.hpp>
#include<iostream>
namespace mylib{
    template<class T>
    struct myclass{};

    template<class P, class T>
    auto operator<<(
        myclass<P>& p,
        boost::phoenix::actor<
            boost::proto::exprns_::basic_expr<
                boost::proto::tagns_::tag::terminal, 
                boost::proto::argsns_::term<T>, 
                0l
            > 
        > const& t
    )->decltype(p){return p << "expression[" << t() << "]";}

}

int main(){
    mylib::myclass<int> A;
    A << boost::phoenix::val(3.); // Doesn't work as expected. Generates a Phoenix `shift``<<` expression. Not the desired outcome.
    mylib::operator<<(A, boost::phoenix::val(3.)); // works as expected
}

One solution is to don't overload a binary operator, but the question is more about how to make Phoenix less greedy.


EDIT: An idiomatic workaround, the hold function:

After several unsuccessful tries I changed my opinion and this seems to be a bad idea since one has to fight hard against the Phoenix/Proto system, in which every C++ expression is interpreting as building a Phoenix expression. So, I decided to define a function to leave the Phoenix world temporarily with a hold function.

namespace boostx{ // warning: the attempt to deal with rvalues in this code could be wrong, feedback is welcomed
namespace phoenixx{
    template<class T> struct held{
        T release_;
        decltype(auto) release(){return release_;}
        decltype(auto) release() const{return release_;}
        friend decltype(auto) release(held& ht){return ht.release();}
        friend decltype(auto) release(held const& ht){return ht.release();} 
    };

    template<class T, typename = std::enable_if_t<boost::phoenix::is_actor<std::decay_t<T>>::value> >
    held<T> hold(T&& t){
        return {std::forward<T>(t)};
    }
}}

(Maybe a function like this already exists (or should exist) in Phoenix, it is complementary to the actor class.)

Then the library has a special overload to handle this held object that is is released in the right context.

namespace mylib{
   ... same as before, plus this new overload ...
    template<class P, class Actor>
    auto operator<<(
        myclass<P>& p,
        boostx::phoenixx::held<Actor> const& ha
    )->decltype(p){
        return mylib::operator<<(p, ha.release());
    }
}

Finally, this works:

int main(){
    mylib::myclass<int> A;
    A << hold(boost::phoenix::val(3.));
    mylib::operator<<(A, boost::phoenix::val(3.)); // works as expected
}

Other functional languages, that I know of, eventually need this kind of functions to suspend eager expression simplification or construction. For example: https://reference.wolfram.com/language/ref/Hold.html

Feedback is welcomed.

Upvotes: 1

Views: 82

Answers (1)

sehe
sehe

Reputation: 393114

In general I think what you're trying to achieve will be better accomplished using a Proto Transform.

Specifically what you're up against is ADL. And there's no way to make Proto "less greedy" about this, since it's a language mechanism[1].

However, ADL should also pull in mylib::operator<<. What gives?

Fix

Your overload takes the actor by const&. Note, however, that if it can be taken by non-const reference, that overload will be preferred. You can take it by value and profit:

Live On Coliru

#include<boost/phoenix.hpp>
#include<iostream>

namespace mylib{

    template<class T>
    struct myclass{};

    template<class P, class T>
    auto operator<<(
        myclass<P>& p,
        boost::phoenix::actor<
            boost::proto::exprns_::basic_expr<
                boost::proto::tagns_::tag::terminal, 
                boost::proto::argsns_::term<T>, 
                0l
            > 
        > t
    )->decltype(p){
        std::cout << __PRETTY_FUNCTION__ << " arg: " << t() << "\n";
        return p; //  << "expression[" << t() << "]";
    }
}

int main(){
    mylib::myclass<int> A;
    A << boost::phoenix::val(3.);
}

The version with universal reference is likely a lot cleaner:

Live On Coliru

template<class P, class Actor>
auto operator<<(myclass<P>& p, Actor&& t) -> decltype(p) {
    std::cout << __PRETTY_FUNCTION__ << " arg: " << t() << "\n";
    return p;
}

You could use tag dispatch or SFINAE to further select on the particular type of Actor

Upvotes: 1

Related Questions