Search code examples
c++boostmetaprogrammingboost-graphboost-fusion

Pointers to class members when iterating with boost::fusion


I have a boost::graph that uses bundled properties like the following:

struct Vertex
{
    std::string id;
};

If I want to use this information in boost::dynamic_properties (e.g. for printing in graphml-format), I can use something like that:

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

int main()
{
    using namespace boost;
    MyGraph g;
    dynamic_properties dp;
    dp.property("id",
        make_transform_value_property_map(
            & myPrettyPrinter<std::string>,
            get(&Vertex::id, g)
        )
    );
}

Since the bundled property might change in the future, I want to be generic about the creation of the dynamic_properties. Therefore, I use boost::fusion

struct Vertex
{
    std::string id;
};


BOOST_FUSION_ADAPT_STRUCT(
    Vertex,
    id
)


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


template <typename T_Seq, typename T_Graph>
void member_iterator(boost::dynamic_properties& dp, T_Graph& g)
{
    using namespace boost;
    using Indices = mpl::range_c<
        unsigned,
        0,
        fusion::result_of::size<T_Seq>::value
    >;

    fusion::for_each(
        Indices(),
        [&](auto i)
        {
            using I = decltype(i);
            dp.property(
                fusion::extension::struct_member_name<T_Seq, i>::call(),
                make_transform_value_property_map(
                    & myPrettyPrinter<
                        typename fusion::result_of::value_at<T_Seq, I>::type
                    >,
                    get(
                        // This works but is not generic,
                        // since it relies on the specific
                        // member name "id":
                        & T_Seq::id,
                        g
                    )
                )
            );
        }
    );
}


int main()
{
    MyGraph g;
    boost::dynamic_properties dp;
    member_iterator<Vertex>(dp, g);
}

My problem is, that I can't find a way to express the line &T_Seq::id in a generic way. I have been looking into fusion::extension::struct_member_name, but was not successful.

I search for either a generic way to replace the problematic line or a different approach entirely to iterate over the members of Vertex.


Solution

  • Regardless of the awesome existing answer, I always hesitate to use macros.

    In this case I noticed everything became difficult because of the boost::property_map<Graph, Tag> interface. I reckoned, you could just use the vertex_bundle_t instead.

    Here's a simple demo using no macros at all, and that works for the vertex and edge bundles. (You could remove the debug output and add the pretty print hook back in).

    Live On Coliru

    #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 {
        std::string more;
        int 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;
    
        using Bundle = typename boost::property_map<T_Graph, Tag>::type;
        using T_Seq  = typename boost::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();
                std::cout << "Adding " << name << "\n";
    
                dp.property(
                    name,
                    make_transform_value_property_map(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);
    }
    

    Prints

    Adding id
    Adding numeric_value
    Adding more
    Adding awesome_sauce