【问题标题】:Pointers to class members when iterating with boost::fusion使用 boost::fusion 迭代时指向类成员的指针
【发布时间】:2016-06-23 23:11:27
【问题描述】:

我有一个 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);
}

我的问题是,我找不到以通用方式表达&amp;T_Seq::id 行的方法。我一直在研究fusion::extension::struct_member_name,但没有成功。

我寻找一种通用方法来替换有问题的行,或者寻找一种完全不同的方法来遍历Vertex 的成员。

【问题讨论】:

  • 遗憾的是,我不知道如何在新的简化 BOOST_FUSION_ADAPT_STRUCT 样式中定义宏。如果您愿意明确说明每种成员类型,我认为this 可以工作。
  • c++98 版本。
  • 谢谢,我可以确认您的解决方案完全符合预期! (仅测试 C++14 版本)。干得好,您绝对应该将该代码放入真正的答案中!
  • 我完全同意

标签: c++ boost metaprogramming boost-graph boost-fusion


【解决方案1】:

据我所知,您无法从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;

}

【讨论】:

  • 酷。交货迅速。又是我可以挑选的东西。也许有一天我会克服对 BOOST_PP 和宏“编程”的恐惧
  • @sehe 如果你有一个周末并能抵抗挫折,看看,IMO 很酷! (对于出色的答案 +1)
  • 我找到了一种方法来实现这一点,这并不完全需要宏:) /cc @Columbo
  • 我喜欢我的代码中的任何内容都无需更改以适应此解决方案。我想接受这两个答案,但由于@sehe 的解决方案似乎更优雅地解决了这个问题,我希望你可以接受他的回答。
【解决方案2】:

不管现有的答案有多棒,我总是对使用宏犹豫不决。

在这种情况下,我注意到由于boost::property_map&lt;Graph, Tag&gt; 接口,一切都变得困难。我想,你可以改用vertex_bundle_t

这是一个完全不使用宏的简单演示,它适用于顶点和边束。 (您可以删除调试输出并重新添加漂亮的打印挂钩)。

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

打印

Adding id
Adding numeric_value
Adding more
Adding awesome_sauce

【讨论】:

  • 我同意,这看起来非常干净和通用。但是,我无法使用 boost::phoenix 调用进行漂亮的打印。如果结果更复杂,我会为此创建一个后续问题。
  • 幸运的是,这很容易,所以如果你遇到这个问题,你可能只想问一个新问题
  • 好的,我刚刚想通了。我的错误是我在尝试std::bindboost::bind。在发现存在phoenix::bind 之后,事情就开始起作用了。
  • Phoenix 比常规绑定更“懒惰”。考虑使用phoenix::function&lt;&gt; 或只是一个可延迟调用的对象以获得更大的灵活性。如果您分享代码,我可以查看它是否可以更友好/优雅
  • 感谢您的提议,我在这里创建了一个后续问题:stackoverflow.com/questions/35919228/…
猜你喜欢
  • 2018-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-20
  • 2014-11-14
  • 2017-07-27
  • 2013-06-06
  • 1970-01-01
相关资源
最近更新 更多