c++boostmetaprogrammingboost-fusionboost-phoenix

Binding a pretty-printer to boost::phoenix actors when iterating with boost::fusion


This question is a follow-up to Pointers to class members when iterating with boost::fusion, where the accepted solution works.

Now, I want not only to add the (primitive) values to the property-map, but use a pretty-printer to improve how the values are displayed. This mechanism will also be used in case the values are not trivial to print.

So, there is some pretty-printer like this:

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

and I expanded the solution from the previous question by binding the prettyPrinter to the boost::phoenix actor:

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

What I am now looking for is the possibility of a more elegant solution, since @sehe pointed out int a comment, that using phoenix::bind might not be the optimal thing to do here.


Solution

  • There are some random errors in the code you show. None of the prettyPrinter overloads would compile. There's no such thing as Seq. You adapt the structs for members that don't exist.

    All these things aside, you are racing a suboptimal line here: function templates and specializations don't mix well (Why not specialize function templates, [HSutter,2001]¹).

    You seem intent on hard-coding your types as well as the pretty-print logic.

    Mantra:

    Type deduction and ADL are your friends for extensible mechanisms.

    I'd write the pretty print interface roughly like:

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

    You can now overload your do_pretty_print

    They will "magically" be picked at the POI.


    Now, I suggest using boost::phoenix::function<> instead of boost::phoenix::bind to arrive at a much cleaner call-site:

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

    Key to the simplification is to let the compiler do what it does best: overload resolution with deduced argument types.

    Full Demo

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

    ¹ See also GotW#49 http://www.gotw.ca/gotw/049.htm and Template Specialization VS Function Overloading e.g.