boost::dynamic_properties and immutable graph object

854 views Asked by At

after implementing some algorithm using the BGL, im trying to provide io functions using GraphML. However, i dont manage to compile a suitable operator<< that takes a const Graph reference.

Here is a boiled down example:

// use bundled properties for vertices and edges
struct VertexProperty
{
   double error;
};

typedef boost::adjacency_list< boost::setS, boost::setS, boost::undirectedS, VertexProperty> Graph;

typedef typename boost::graph_traits<Graph>::edge_descriptor edge_descriptor;
typedef typename boost::graph_traits<Graph>::vertex_descriptor vertex_descriptor;

std::ostream& operator<<(std::ostream& os, const Graph& graph)
{
    typedef std::map<vertex_descriptor, std::size_t> IndexMap;
    IndexMap index_map;
    boost::associative_property_map<IndexMap> index_properties(index_map);

    std::size_t i = 0;
    for (const vertex_descriptor& v : boost::make_iterator_range(boost::vertices(graph)))
        index_properties[v] = i++;

    boost::dynamic_properties dp;
    typename boost::property_map<Graph, double VertexProperty::*>::const_type error_map = get(&VertexProperty::error, graph);

    dp.property("error", error_map);

    boost::write_graphml(os, graph,index_properties,dp);

    return os;
}

int main()
{
  Graph g;
  std::cout << g <<std::endl;
}

Compilation fails with:

boost/property_map/property_map.hpp:309:44: error: assignment of read-only location '(&((const boost::adj_list_vertex_property_map, double, const double&, double VertexProperty::*>&)pa))->boost::adj_list_vertex_property_map::operator[], double, const double&, double VertexProperty::*>(k)' static_cast(pa)[k] = v;

As far as i understood the dynamic_properties documentation, those read only checks are supposed to happen at runtime (Isn't this one of the aims of the whole type erasure). And of course they should fail if one tries to modify a immutable property. But the call to wirte write_graphml() takes a const ref to the dynamics properties and is not supposed to change anything.

To state the question(s):

  • Why does compilation fail?
  • And how do i do it corretly?
  • By using some other property_map (Yes/No/Which one)?

For a (not) running example @ coliru.stacked-crooked.com: See here!

Regards, Marti

1

There are 1 answers

1
sehe On BEST ANSWER

The true issue at hand is that the category of the vertex property map is being deduced as LvaluePropertyMap (which it is).

However, the LvaluePropertyMap concept promises to be a superset of ReadablePropertyMap and WritablePropertyMap. This poses problems when the const_type of the graph properties are used: they are lvalues, but they are not writable.

The only working solution I came up with was to wrap the property map type to overrule the category:

namespace detail {
    template <typename Map>
        struct readable_only_pmap : Map {
            readable_only_pmap(Map map) : Map(map) { }

            // overrule the category tag
            typedef boost::readable_property_map_tag category;
        };
}

Now, you can use it like so:

using pmap_t = typename boost::property_map<Graph, double VertexProperty::*>::const_type;
detail::readable_only_pmap<pmap_t> error_map = get(&VertexProperty::error, graph);

Although it's nearly the same, now dynamic_properties::property detects that the map is readable only, and doesn't attempt to generate the put helpers (instead, an exception will be raised if a put is attempted).

Full Code with demo graph:

Live On Coliru

#include <iostream>
#include <string>
#include <vector>
#include <functional>

#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphml.hpp>
// #include <Eigen/Core>

// use bundled properties for vertices and edges
struct VertexProperty
{
    double error;
    // Eigen::Matrix<real,dim,1> location;
};

typedef boost::adjacency_list< boost::setS, boost::setS, boost::undirectedS, VertexProperty> Graph;

typedef typename boost::graph_traits<Graph>::edge_descriptor edge_descriptor;
typedef typename boost::graph_traits<Graph>::vertex_descriptor vertex_descriptor;

namespace detail {
    template <typename Map>
        struct readable_only_pmap : Map {
            readable_only_pmap(Map map) : Map(map) { }

            // overrule the category tag
            typedef boost::readable_property_map_tag category;
        };
}

std::ostream& operator<<(std::ostream& os, const Graph& graph)
{
    typedef std::map<vertex_descriptor, std::size_t> IndexMap;
    IndexMap index_map;
    boost::associative_property_map<IndexMap> index_properties(index_map);

    std::size_t i = 0;
    for (const vertex_descriptor& v : boost::make_iterator_range(boost::vertices(graph)))
        index_properties[v] = i++;

    using pmap_t = typename boost::property_map<Graph, double VertexProperty::*>::const_type;
    detail::readable_only_pmap<pmap_t> error_map = get(&VertexProperty::error, graph);

    boost::dynamic_properties dp;
    dp.property("error", error_map);
    boost::write_graphml(os, graph, index_properties, dp);

    return os;
}

int main()
{
  Graph g;
  auto v1 = boost::add_vertex(VertexProperty{0.1}, g);
  auto v2 = boost::add_vertex(VertexProperty{0.2}, g);
  auto v3 = boost::add_vertex(VertexProperty{0.3}, g);
  auto v4 = boost::add_vertex(VertexProperty{0.4}, g);
  auto v5 = boost::add_vertex(VertexProperty{0.5}, g);

  add_edge(v1,v2,g);
  add_edge(v5,v2,g);
  add_edge(v4,v2,g);
  add_edge(v2,v3,g);
  add_edge(v3,v4,g);
  add_edge(v4,v1,g);

  std::cout << g <<std::endl;
}

Output: (slightly reformatted)

<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
<key id="key0" for="node" attr.name="error" attr.type="double" />
<graph id="G" edgedefault="undirected" parse.nodeids="free" parse.edgeids="canonical" parse.order="nodesfirst">
    <node id="n0"> <data key="key0">0.1</data> </node>
    <node id="n1"> <data key="key0">0.2</data> </node>
    <node id="n2"> <data key="key0">0.3</data> </node>
    <node id="n3"> <data key="key0">0.4</data> </node>
    <node id="n4"> <data key="key0">0.5</data> </node>
    <edge id="e0" source="n0" target="n1"> </edge>
    <edge id="e1" source="n4" target="n1"> </edge>
    <edge id="e2" source="n3" target="n1"> </edge>
    <edge id="e3" source="n1" target="n2"> </edge>
    <edge id="e4" source="n2" target="n3"> </edge>
    <edge id="e5" source="n3" target="n0"> </edge>
</graph>
</graphml>