Можно ли сделать Феникс на несколько ступеней менее жадным в отношении бинарных операторов?

Я хотел иметь категорию класса, которая принимает (неоцененные) выражения Феникса через бинарный оператор. По сути, идея заключается в том, что класс обрабатывает выражения и, например, выводит выражение на экран.

Проблема в том, что Phoenix перегружает все бинарные операторы и, если нет точного соответствия, оператор Phoenix (lazy) предпочтителен. Можно ли сделать "Феникс" чуть менее жадным до угона операторов?

Пример кода:

#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++ интерпретирует как построение выражения Феникса. Итак, я решил определить функцию, чтобы временно покинуть мир Феникса с помощью функции удержания.

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

(Может быть, такая функция уже существует (или должна существовать) в Фениксе, она дополняет actor учебный класс.)

Тогда библиотека имеет специальную перегрузку для обработки этого held объект, который является release г в правильном контексте.

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

Обратная связь приветствуется.

1 ответ

Решение

В общем, я думаю, что то, что вы пытаетесь достичь, будет лучше достигнуто с помощью Proto Transform.

В частности, вы против ADL. И нет никакого способа сделать Прото "менее жадным" по этому поводу, поскольку это языковой механизм[1].

Тем не менее, ADL должен также подтянуть mylib::operator<<, Что дает?

исправлять

Ваша перегрузка забирает актера 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 для дальнейшего выбора конкретного типа актера.

Другие вопросы по тегам