Reputation: 16242
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 release
d 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
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?
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:
#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:
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