Привязка симпатичного принтера к актерам boost::phoenix при итерации с boost::fusion

Этот вопрос является продолжением указателей на членов класса при повторении с boost::fusion, где работает принятое решение.

Теперь я хочу не только добавить (примитивные) значения в карту свойств, но и использовать симпатичный принтер для улучшения отображения значений. Этот механизм также будет использоваться в случае, если значения не являются тривиальными для печати.

Итак, есть такой симпатичный принтер:

template<typename T>
std::string prettyPrinter(const T& t);

template<>
std::string prettyPrinter(const std::string& s)
{
    return "The string id: " + s;
}

template<>
std::string prettyPrinter(const int& i)
{
    return "The int id: " + std::to_string(i);
}

и я расширил решение предыдущего вопроса, привязав prettyPrinter к актеру boost::phoenix:

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/find.hpp>
#include <boost/phoenix/fusion/at.hpp>
#include <boost/phoenix.hpp>
#include <boost/mpl/range_c.hpp>

#include <iostream>

struct Vertex {
    std::string id;
};

struct Edge {
    int id;
};

BOOST_FUSION_ADAPT_STRUCT(Vertex, id)
BOOST_FUSION_ADAPT_STRUCT(Edge, id)

template <typename Tag, typename T_Graph>
void member_iterator(boost::dynamic_properties& dp, T_Graph& g)
{
    using namespace boost;

    using Bundle = typename property_map<T_Graph, Tag>::type;
    using T_Seq  = typename property_traits<Bundle>::value_type;

    using Indices = mpl::range_c<unsigned, 0, fusion::result_of::size<T_Seq>::value>;

    fusion::for_each(
        Indices{},
        [&, bundle=get(Tag{}, g)](auto i) {
            auto name = fusion::extension::struct_member_name<T_Seq, i>::call();
            using TempType = typename fusion::result_of::value_at<T_Seq, decltype(i)>::type;

            //
            // Interesting part starts here:
            //
            dp.property(
                name,
                make_transform_value_property_map(
                    phoenix::bind(
                        prettyPrinter<TempType>,
                        phoenix::at_c<i>(phoenix::arg_names::arg1)
                    ),
                    bundle
                )
            );
        }
    );
}

using MyGraph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Vertex, Edge>;

int main()
{
    MyGraph g;
    boost::dynamic_properties dp;

    member_iterator<boost::vertex_bundle_t>(dp, g);
    member_iterator<boost::edge_bundle_t>(dp, g);
}

Сейчас я ищу возможность более элегантного решения, поскольку @sehe указал в комментарии, что использование phoenix::bind может быть здесь не оптимальным.


person Slizzered    schedule 10.03.2016    source источник


Ответы (1)


В коде, который вы показываете, есть некоторые случайные ошибки. Ни одна из перегрузок prettyPrinter не скомпилируется. Нет такого понятия, как Seq. Вы адаптируете структуры для членов, которых не существует.

Если отбросить все эти вещи в сторону, здесь вы ведете неоптимальную линию: шаблоны функций и специализации плохо сочетаются (Почему бы не специализировать шаблоны функций, [HSutter, 2001]¹).

Похоже, вы стремитесь жестко запрограммировать свои типы, а также логику красивой печати.

Мантра:

Вывод типов и ADL — ваши друзья в расширяемых механизмах.

Я бы написал красивый интерфейс печати примерно так:

#include <string>

namespace pretty_printing
{
    namespace default_impl {
        std::string do_pretty_print(const std::string& s) {
            return "The string id: " + s;
        }

        std::string do_pretty_print(const int i) {
            return "The int id: " + std::to_string(i);
        }
    }

    struct pretty_print_f {
        using result_type = std::string;

        template <typename T> result_type operator()(T&& v) const { 
            using namespace default_impl; // enable ADL
            return do_pretty_print(std::forward<T>(v));
        }
    };
}

Теперь вы можете перегрузить свой do_pretty_print

  • любым способом (в том числе путем предоставления общих перегрузок)
  • в безопасных местах, например, как friend члены ваших типов или как (шаблонные) функции в связанном(ых) пространстве(ях) имен ваших типов

Они будут "волшебным образом" выбраны в POI.


Теперь я предлагаю использовать boost::phoenix::function<> вместо boost::phoenix::bind, чтобы получить более чистый сайт вызова:

auto name = fusion::extension::struct_member_name<T_Seq, i>::call();
px::function<pretty_printing::pretty_print_f> pretty_print;

dp.property(
    name,
    make_transform_value_property_map(pretty_print(px::at_c<i>(arg1)), bundle)
);

Ключ к упрощению заключается в том, чтобы позволить компилятору делать то, что он делает лучше всего: перегружать разрешение с помощью выведенных типов аргументов.

Полная демонстрация

Прямой эфир на Coliru

#include <string>

namespace pretty_printing
{
    namespace default_impl {
        std::string do_pretty_print(const std::string& s) {
            return "The string id: " + s;
        }

        std::string do_pretty_print(const int i) {
            return "The int id: " + std::to_string(i);
        }
    }

    struct pretty_print_f {
        using result_type = std::string;

        template <typename T> result_type operator()(T&& v) const { 
            using namespace default_impl;
            return do_pretty_print(std::forward<T>(v));
        }
    };
}

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/find.hpp>
#include <boost/phoenix/fusion/at.hpp>
#include <boost/phoenix.hpp>
#include <boost/mpl/range_c.hpp>

#include <iostream>

struct Vertex {
    std::string id;
    int numeric_value;
};

struct Edge {
    int more;
    std::string awesome_sauce;
};

BOOST_FUSION_ADAPT_STRUCT(Vertex, id, numeric_value)
BOOST_FUSION_ADAPT_STRUCT(Edge, more, awesome_sauce)

template <typename Tag, typename T_Graph>
void member_iterator(boost::dynamic_properties& dp, T_Graph& g)
{
    using namespace boost;
    namespace px = boost::phoenix;
    using namespace px::arg_names;

    using Bundle = typename property_map<T_Graph, Tag>::type;
    using T_Seq  = typename property_traits<Bundle>::value_type;

    using Indices = mpl::range_c<unsigned, 0, fusion::result_of::size<T_Seq>::value>;

    fusion::for_each(
        Indices{},
        [&, bundle=get(Tag{}, g)](auto i) {

            auto name = fusion::extension::struct_member_name<T_Seq, i>::call();
            px::function<pretty_printing::pretty_print_f> pretty_print;

            dp.property(
                name,
                make_transform_value_property_map(pretty_print(px::at_c<i>(arg1)), bundle)
            );
        }
    );
}

using MyGraph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Vertex, Edge>;

int main()
{
    MyGraph g;
    boost::dynamic_properties dp;

    member_iterator<boost::vertex_bundle_t>(dp, g);
    member_iterator<boost::edge_bundle_t>(dp, g);
}

¹ См. также GotW#49 http://www.gotw.ca/gotw/049.htm и Специализация шаблона VS Перегрузка функции, например

person sehe    schedule 10.03.2016
comment
Это значительно улучшило мой фактический код! Я обязательно прочитаю ваши источники, спасибо! - person Slizzered; 11.03.2016