Указатели на члены класса при повторении с помощью boost::fusion

У меня есть boost::graph, который использует связанные свойства, такие как следующие:

struct Vertex
{
    std::string id;
};

Если я хочу использовать эту информацию в boost::dynamic_properties (например, для печати в формате graphml), я могу использовать что-то вроде этого:

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

Поскольку связанное свойство может измениться в будущем, я хочу быть универсальным в отношении создания файла dynamic_properties. Поэтому я использую 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);
}

Моя проблема в том, что я не могу найти способ выразить строку &T_Seq::id в общем виде. Я искал fusion::extension::struct_member_name, но безуспешно.

Я ищу либо общий способ замены проблемной строки, либо другой подход, полностью перебирающий элементы Vertex.


person Slizzered    schedule 09.03.2016    source источник
comment
К сожалению, я не знаю, как определить макрос в новом упрощенном стиле BOOST_FUSION_ADAPT_STRUCT. Если вы хотите явно указать каждый тип члена, я думаю, это может сработать.   -  person llonesmiz    schedule 09.03.2016
comment
Версия C++98.   -  person llonesmiz    schedule 09.03.2016
comment
Спасибо, я могу подтвердить, что ваше решение работает именно так, как ожидалось! (тестировалась только версия C++14). Отличная работа, и вы обязательно должны использовать этот код в реальном ответе!   -  person Slizzered    schedule 09.03.2016
comment
я полностью согласен   -  person sehe    schedule 09.03.2016


Ответы (2)


Независимо от потрясающего существующего ответа, я всегда не решаюсь использовать макросы.

В этом случае я заметил, что все усложнилось из-за интерфейса boost::property_map<Graph, Tag>. Я подумал, что вместо этого вы могли бы просто использовать vertex_bundle_t.

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

Жить на 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);
}

Отпечатки

Adding id
Adding numeric_value
Adding more
Adding awesome_sauce
person sehe    schedule 09.03.2016
comment
Я согласен, это выглядит очень чистым и общим. Однако я не могу заставить красивую печать работать с вызовами boost::phoenix. Если это окажется более сложным, я создам для этого дополнительный вопрос. - person Slizzered; 10.03.2016
comment
К счастью, это простая часть, поэтому вы можете просто задать новый вопрос, если застряли с этим. - person sehe; 10.03.2016
comment
Хорошо, я только что понял это. Моя ошибка заключалась в том, что я пробовал std::bind и boost::bind. Узнав, что phoenix::bind существует, все заработало. - person Slizzered; 10.03.2016
comment
Phoenix — уровень более ленивый, чем обычные бинды. Рассмотрите возможность использования phoenix::function<> или просто объекта с отложенным вызовом для большей гибкости. Если вы поделитесь кодом, я могу проверить, может ли он быть более дружелюбным/элегантным. - person sehe; 10.03.2016
comment
спасибо за предложение, я создал дополнительный вопрос здесь: stackoverflow.com/questions/35919228/ - person Slizzered; 10.03.2016
comment
Я думаю, что это стоило вопроса! Я тоже очень доволен моим ответом /cc @cv_and_he - person sehe; 10.03.2016

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

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

Чтобы сгенерировать эту структуру, я использую следующие макросы:


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

Этот макрос принимает имя структуры и последовательность пар (type,name), просто открывает пространство имен your_ns и вызывает CREATE_POINTER_TO_MEMBER_TRAIT, при этом каждая пара передает STRUCT_NAME в качестве данных.


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

Это то, что фактически создает черту. Он принимает параметр R (я понятия не имею, что он делает), имя структуры, которую вы адаптируете, индекс текущего члена и пару (type,name). Он использует BOOST_PP_TUPLE_ELEM(2,N,TYPE_AND_NAME) для получения либо типа, либо имени члена.


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

}
person Community    schedule 09.03.2016
comment
Прохладный. Быстрая доставка. Опять что-то, что я могу разобрать. Может быть, однажды я преодолею свою фобию BOOST_PP и макропрограмм в целом. - person sehe; 09.03.2016
comment
@sehe Если у вас есть выходные и сопротивление разочарованию, взгляните, это довольно круто, ИМО! (И +1 за отличный ответ) - person Columbo; 09.03.2016
comment
Я нашел способ добиться этого, для которого не нужны макросы :) /cc @Columbo - person sehe; 09.03.2016
comment
Мне нравится, что ничего в моем коде не пришлось менять для адаптации этого решения. Я хотел бы принять оба ответа, но, поскольку решение @sehe, похоже, решает проблему более элегантно, я надеюсь, что вы согласны, что я принимаю его ответ. - person Slizzered; 10.03.2016