是否有可能让 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 对象,它在正确的上下文中是 released。

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 引用获取,则该重载将是首选。你可以按价值和利润来拿:

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.);
}

具有通用参考的版本可能更干净:

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;
}

您可以使用标签分派或 SFINAE 进一步 select 特定类型的 Actor