是否有可能让 Phoenix 对二元运算符的贪婪程度降低一个档次?
Is it possible to make Phoenix a notch less greedy about binary operators?
我想要一个 class 类别,它通过二元运算符获取(未评估的)Phoenix 表达式。基本上这个想法是 class 处理表达式,例如,将表达式打印到屏幕上。
问题是 Phoenix 重载了所有二元运算符,除非存在精确匹配,否则 Phoenix(惰性)运算符是首选。 有没有可能让 Phoenix 对劫持运营商不那么贪心?
示例代码:
#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
}
一个解决方案是不要重载二元运算符,但问题更多是关于如何让 Phoenix 不那么贪婪。
编辑:惯用的解决方法,hold
函数:
在几次不成功的尝试之后,我改变了我的看法,这似乎是一个坏主意,因为必须与 Phoenix/Proto 系统作斗争,在该系统中,每个 C++ 表达式都被解释为构建 Phoenix 表达式。所以,我决定定义一个函数,暂时离开凤凰世界,带hold函数。
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)};
}
}}
(也许Phoenix中已经存在(或应该存在)这样的功能,它是对actor
class的补充。)
然后库有一个特殊的重载来处理这个 held
对象,它在正确的上下文中是 release
d。
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());
}
}
终于成功了:
int main(){
mylib::myclass<int> A;
A << hold(boost::phoenix::val(3.));
mylib::operator<<(A, boost::phoenix::val(3.)); // works as expected
}
据我所知,其他函数式语言最终需要这种函数来暂停急切的表达式简化或构造。例如:https://reference.wolfram.com/language/ref/Hold.html
欢迎反馈。
总的来说,我认为使用 Proto Transform 可以更好地完成您想要实现的目标。
具体来说,您要对抗的是 ADL。而且没有办法制作 Proto "less greedy",因为它是一种语言机制[1].
不过ADL也要拉进来mylib::operator<<
。给出了什么?
修复
您的重载占用了 actor const&
。但是请注意,如果它可以被非 const 引用获取,则该重载将是首选。你可以按价值和利润来拿:
#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.);
}
具有通用参考的版本可能更干净:
template<class P, class Actor>
auto operator<<(myclass<P>& p, Actor&& t) -> decltype(p) {
std::cout << __PRETTY_FUNCTION__ << " arg: " << t() << "\n";
return p;
}
您可以使用标签分派或 SFINAE 进一步 select 特定类型的 Actor
我想要一个 class 类别,它通过二元运算符获取(未评估的)Phoenix 表达式。基本上这个想法是 class 处理表达式,例如,将表达式打印到屏幕上。
问题是 Phoenix 重载了所有二元运算符,除非存在精确匹配,否则 Phoenix(惰性)运算符是首选。 有没有可能让 Phoenix 对劫持运营商不那么贪心?
示例代码:
#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
}
一个解决方案是不要重载二元运算符,但问题更多是关于如何让 Phoenix 不那么贪婪。
编辑:惯用的解决方法,hold
函数:
在几次不成功的尝试之后,我改变了我的看法,这似乎是一个坏主意,因为必须与 Phoenix/Proto 系统作斗争,在该系统中,每个 C++ 表达式都被解释为构建 Phoenix 表达式。所以,我决定定义一个函数,暂时离开凤凰世界,带hold函数。
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)};
}
}}
(也许Phoenix中已经存在(或应该存在)这样的功能,它是对actor
class的补充。)
然后库有一个特殊的重载来处理这个 held
对象,它在正确的上下文中是 release
d。
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());
}
}
终于成功了:
int main(){
mylib::myclass<int> A;
A << hold(boost::phoenix::val(3.));
mylib::operator<<(A, boost::phoenix::val(3.)); // works as expected
}
据我所知,其他函数式语言最终需要这种函数来暂停急切的表达式简化或构造。例如:https://reference.wolfram.com/language/ref/Hold.html
欢迎反馈。
总的来说,我认为使用 Proto Transform 可以更好地完成您想要实现的目标。
具体来说,您要对抗的是 ADL。而且没有办法制作 Proto "less greedy",因为它是一种语言机制[1].
不过ADL也要拉进来mylib::operator<<
。给出了什么?
修复
您的重载占用了 actor const&
。但是请注意,如果它可以被非 const 引用获取,则该重载将是首选。你可以按价值和利润来拿:
#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.);
}
具有通用参考的版本可能更干净:
template<class P, class Actor>
auto operator<<(myclass<P>& p, Actor&& t) -> decltype(p) {
std::cout << __PRETTY_FUNCTION__ << " arg: " << t() << "\n";
return p;
}
您可以使用标签分派或 SFINAE 进一步 select 特定类型的 Actor