简体   繁体   中英

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 (eg 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 .

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

As far as I know you can't get a pointer to a member from the information BOOST_FUSION_ADAPT_STRUCT stores internally(you can, as you discovered, get the member type or name). One possible way to approach this problem is creating a macro that invokes BOOST_FUSION_ADAPT_STRUCT and then stores the additional information. I've chosen to store it like this:

template<>
struct pointer_to_member_N<Vertex,0>
{
    static constexpr std::string Vertex::* value = &Vertex::id;
};

In order to generate that struct I use the following macros:


#define YOUR_NS_SAVE_MEMBERPTR(STRUCT_NAME,MEMBERS) \
namespace your_ns { \
BOOST_PP_SEQ_FOR_EACH_I(CREATE_POINTER_TO_MEMBER_TRAIT,STRUCT_NAME,BOOST_PP_CAT(YOUR_NS_SAVE_MEMBERPTR_FILLER_0 MEMBERS,_END)) \
}

This macro takes the struct name and a sequence of pairs (type,name) , and simply opens namespace your_ns and calls CREATE_POINTER_TO_MEMBER_TRAIT with each pair passing STRUCT_NAME as data.


#define CREATE_POINTER_TO_MEMBER_TRAIT(R,STRUCT_NAME,INDEX,TYPE_AND_NAME) \
template <> struct pointer_to_member_N<STRUCT_NAME, INDEX>{ static constexpr BOOST_PP_TUPLE_ELEM(2,0,TYPE_AND_NAME) STRUCT_NAME::* value = &STRUCT_NAME::BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME); };

This is the one that actually creates the trait. It takes a parameter R (that I have no idea what it does), the name of the struct you are adapting, the index of the current member and a pair (type,name) . It uses BOOST_PP_TUPLE_ELEM(2,N,TYPE_AND_NAME) to get either the type or the name of the member.


#include <iostream> 
#include <string>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/at.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/mpl.hpp>

#include <boost/mpl/range_c.hpp>

#include <cstddef>

namespace your_ns
{
    template <typename StructName, int N>
    struct pointer_to_member_N;
}

//Heavily "inspired" from BOOST_FUSION_ADAPT_STRUCT
#define YOUR_NS_SAVE_MEMBERPTR_FILLER_0(X, Y)  \
    ((X, Y)) YOUR_NS_SAVE_MEMBERPTR_FILLER_1
#define YOUR_NS_SAVE_MEMBERPTR_FILLER_1(X, Y)  \
    ((X, Y)) YOUR_NS_SAVE_MEMBERPTR_FILLER_0
#define YOUR_NS_SAVE_MEMBERPTR_FILLER_0_END
#define YOUR_NS_SAVE_MEMBERPTR_FILLER_1_END

#define CREATE_POINTER_TO_MEMBER_TRAIT(R,STRUCT_NAME,INDEX,TYPE_AND_NAME) \
template <> struct pointer_to_member_N<STRUCT_NAME, INDEX>{ static constexpr BOOST_PP_TUPLE_ELEM(2,0,TYPE_AND_NAME) STRUCT_NAME::* value = &STRUCT_NAME::BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME); };

#define YOUR_NS_SAVE_MEMBERPTR(STRUCT_NAME,MEMBERS) \
namespace your_ns { \
BOOST_PP_SEQ_FOR_EACH_I(CREATE_POINTER_TO_MEMBER_TRAIT,STRUCT_NAME,BOOST_PP_CAT(YOUR_NS_SAVE_MEMBERPTR_FILLER_0 MEMBERS,_END)) \
}


#define ADAPT_STRUCT_AND_SAVE_MEMBERPTR(TYPE,MEMBERS) BOOST_FUSION_ADAPT_STRUCT(TYPE,MEMBERS) YOUR_NS_SAVE_MEMBERPTR(TYPE,MEMBERS)

   struct Vertex {
      std::string id;  
      std::size_t index;  
    };


ADAPT_STRUCT_AND_SAVE_MEMBERPTR(
  Vertex, 
  (std::string, id)
  (std::size_t, index)
)





int main() {
Vertex v; 
v.id="A";
v.index=0;

std::cout << std::mem_fn(your_ns::pointer_to_member_N<Vertex,0>::value)(v) << std::endl;
std::cout << v.*your_ns::pointer_to_member_N<Vertex,1>::value << std::endl;

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM