Objective:
I want to wrap a Boost Graph into a class on which I have a bit more control, reduce the noise in the main function, and print a graphviz format.
Problem
The code fails to compile, returning a include/boost/graph/detail/adjacency_list.hpp:2601:27: error: cannot form a reference to 'void'
Minimal reproducible example
The code compiles on Compiler Explorer https://godbolt.org/z/9beKovxc9 But if you uncomment the last line of main the error is reproduced.
// BGL
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/visitors.hpp>
#include <boost/graph/breadth_first_search.hpp>
#include <boost/graph/graphviz.hpp>
#include <iostream> // std::cout
#include <functional> // std::function
#include <utility> // std::pair
///
/// @brief Defines the desired properties and constraints for a coalescent graph
///
struct tree_traits
{
///
/// @brief Trees are sparse graph in nature, adjacency_matrix would not be justified here
///
template<class... Types>
using implementation = boost::adjacency_list<Types...>;
///
/// @brief We want to enforce avoiding multi-graphs (edges with same end nodes)
///
using out_edge_list_type = boost::setS;
///
/// @brief We are prioritizing speed for space
///
using vertex_list_type = boost::listS;
///
/// @brief Coalescent trees are directed acyclic graphs
///
using directed_type = boost::directedS ;
}; // end tree_traits
///
/// @brief A graph with properties suited to represent a coalescent tree
///
template<class VertexProperties=boost::no_property, class EdgeProperties=boost::no_property>
class Tree
{
public:
///
/// @brief Properties of an edge, like the demes visited
/// @see https://www.boost.org/doc/libs/1_80_0/libs/graph/doc/bundles.html
///
using edge_properties = EdgeProperties;
///
/// @brief Properties of a vertex, like the mutational state
/// @see https://www.boost.org/doc/libs/1_80_0/libs/graph/doc/bundles.html
///
using vertex_properties = VertexProperties;
///
/// @brief The type of graph hold by the tree class
///
using graph_type = tree_traits::implementation
<
tree_traits::out_edge_list_type,
tree_traits::vertex_list_type,
tree_traits::directed_type,
VertexProperties,
EdgeProperties
>;
private:
graph_type _graph;
static constexpr bool vertex_prop_undefined = std::is_same<vertex_properties,boost::no_property>::value;
static constexpr bool edge_prop_undefined = std::is_same<edge_properties,boost::no_property>::value;
public:
/// @note adds an object-oriented layer
auto add_vertex(auto&&... args)
{
return boost::add_vertex(std::forward<decltype(args)>(args)..., _graph);
}
/// @note adds an object-oriented layer
/// @note order of vertices determines edge orientation
auto add_edge(auto&&... args)
{
return boost::add_edge(std::forward<decltype(args)>(args)..., _graph);
}
void to_graphviz(std::ostream& out) const
{
using namespace boost;
return boost::write_graphviz(out, _graph,
boost::make_label_writer(boost::get(vertex_bundle, this->_graph)),
boost::make_label_writer(boost::get(edge_bundle, this->_graph))
);
}
};
int main()
{
using q_tree_type = Tree<std::string, double>;
q_tree_type graph;
// We insert the root of tree
auto root = graph.add_vertex("first");
//graph.to_graphviz(std::cout); // <--- huho
}
What I can think of
- I thought that (maybe) having a pure
std::stringasVertexPropertiesrather than a custom structure confuses the compiler. I tried to disambiguate withif constexpr, to use adefault_writerif theVertexPropertiestype evaluated tono_property. But no success on this front, but here is the embarassingly naive code I wrote
// member of Tree<...> class
void to_graphviz(std::ostream& out) const
{
using namespace boost;
if constexpr (vertex_prop_undefined)
{
if constexpr (edge_prop_undefined)
return write_graphviz(out, _graph);
return boost::write_graphviz(out, _graph, default_writer(), boost::make_label_writer(boost::get(edge_bundle, this->_graph)));
}
if constexpr (edge_prop_undefined)
return write_graphviz(out, _graph, boost::make_label_writer(boost::get(vertex_bundle, this->_graph)));
return boost::write_graphviz(out, _graph, boost::make_label_writer(boost::get(vertex_bundle, this->_graph)), boost::make_label_writer(boost::get(edge_bundle, this->_graph)));
}
CodePudding user response:
Your problem has nothing to do with displaying the bundle. std::string and double are both output-streamable, so they will work fine.
Your problem is the classical one: you selected a graph model with no suitable implicit vertex index. This is due to the choice:
///
/// @brief We are prioritizing speed for space
///
using vertex_list_type = boost::listS;
Note that, at least in general terms, the comment claim is pretty inaccurate. The sole reason to select
listSis because
- insert/erase can be more consistent time (but slower),
- vertex descriptors and iterators can be stable (unless erased)
But for now, let's demonstrate that write_graphviz already works with vecS instead:
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <iostream>
struct tree_traits {
template <class... Types> using model = boost::adjacency_list<Types...>;
using out_edge_list_type = boost::setS;
using vertex_list_type = boost::vecS; // listS;
using directed_type = boost::directedS;
};
template <class VertexProperties = boost::no_property,
class EdgeProperties = boost::no_property>
struct Tree {
using edge_properties = EdgeProperties; // like the demes visited
using vertex_properties = VertexProperties; // like the mutational state
using graph_type =
tree_traits::model<tree_traits::out_edge_list_type, tree_traits::vertex_list_type,
tree_traits::directed_type, VertexProperties, EdgeProperties>;
private:
graph_type _graph;
static constexpr bool vertex_prop_undefined = std::is_same_v<vertex_properties, boost::no_property>;
static constexpr bool edge_prop_undefined = std::is_same_v<edge_properties, boost::no_property>;
public:
/// @note adds an object-oriented layer
auto add_vertex(auto&&... args) {
return boost::add_vertex(std::forward<decltype(args)>(args)..., _graph);
}
/// @note adds an object-oriented layer
/// @note order of vertices determines edge orientation
auto add_edge(auto&&... args) {
return boost::add_edge(std::forward<decltype(args)>(args)..., _graph);
}
void to_graphviz(std::ostream& out) const {
using namespace boost;
return boost::write_graphviz(
out, _graph,
boost::make_label_writer(boost::get(vertex_bundle, this->_graph)),
boost::make_label_writer(boost::get(edge_bundle, this->_graph)));
}
};
int main() {
using q_tree_type = Tree<std::string, double>;
q_tree_type tree;
auto first = tree.add_vertex("first");
auto second = tree.add_vertex("second");
auto third = tree.add_vertex("second");
tree.add_edge(first, second, 444.4);
tree.add_edge(first, third, 555.5);
tree.to_graphviz(std::cout);
}
Prints
digraph G {
0[label=first];
1[label=second];
2[label=second];
0->1 [label=444.39999999999998];
0->2 [label=555.5];
}
Workarounds?
I'd stick with vecS. The interface of Tree<> doesn't allow for inserting vertices except at the end. You currently don't show interface for removing vertices. This means that neither reallocation cost nor stability are reasons for preferring listS.
If you must use a node-based vertex container selector, you're responsible for adding an index for the algorithms that require it:
void to_graphviz(std::ostream& out) const {
boost::container::flat_map<typename graph_type::vertex_descriptor, size_t> index;
index.reserve(num_vertices(_graph));
// OR just: std::map<typename graph_type::vertex_descriptor, size_t> index;
for (size_t i = 0; auto v : boost::make_iterator_range(vertices(_graph)))
index[v] = i ;
return write_graphviz( //
out, _graph, //
make_label_writer(get(boost::vertex_bundle, _graph)), //
make_label_writer(get(boost::edge_bundle, _graph)), //
boost::default_writer{}, //
boost::make_assoc_property_map(index));
}
Live Demo
Putting that in action as well:
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <iostream>
#include <numeric>
#include <boost/container/flat_map.hpp>
#include <boost/property_map/property_map.hpp>
struct tree_traits {
template <class... Types> using model = boost::adjacency_list<Types...>;
using out_edge_list_type = boost::setS;
using vertex_list_type = boost::listS;
using directed_type = boost::directedS;
};
template <class VertexProperties = boost::no_property,
class EdgeProperties = boost::no_property>
struct Tree {
using edge_properties = EdgeProperties; // like the demes visited
using vertex_properties = VertexProperties; // like the mutational state
using graph_type =
tree_traits::model<tree_traits::out_edge_list_type, tree_traits::vertex_list_type,
tree_traits::directed_type, VertexProperties, EdgeProperties>;
private:
graph_type _graph;
static constexpr bool vertex_prop_undefined = std::is_same_v<vertex_properties, boost::no_property>;
static constexpr bool edge_prop_undefined = std::is_same_v<edge_properties, boost::no_property>;
public:
/// @note adds an object-oriented layer
auto add_vertex(auto&&... args) {
return boost::add_vertex(std::forward<decltype(args)>(args)..., _graph);
}
/// @note adds an object-oriented layer
/// @note order of vertices determines edge orientation
auto add_edge(auto&&... args) {
return boost::add_edge(std::forward<decltype(args)>(args)..., _graph);
}
void to_graphviz(std::ostream& out) const {
boost::container::flat_map<typename graph_type::vertex_descriptor, size_t> index;
index.reserve(num_vertices(_graph));
// OR just: std::map<typename graph_type::vertex_descriptor, size_t> index;
for (size_t i = 0; auto v : boost::make_iterator_range(vertices(_graph)))
index[v] = i ;
return write_graphviz( //
out, _graph, //
make_label_writer(get(boost::vertex_bundle, _graph)), //
make_label_writer(get(boost::edge_bundle, _graph)), //
boost::default_writer{}, //
boost::make_assoc_property_map(index));
}
};
int main() {
using q_tree_type = Tree<std::string, double>;
q_tree_type tree;
auto first = tree.add_vertex("first");
auto second = tree.add_vertex("second");
auto third = tree.add_vertex("second");
tree.add_edge(first, second, 444.4);
tree.add_edge(first, third, 555.5);
tree.to_graphviz(std::cout);
}
Prints
digraph G {
0[label=first];
1[label=second];
2[label=second];
0->1 [label=444.39999999999998];
0->2 [label=555.5];
}
Notes
Note that most algorithms require the vertex index. E.g. breadth_first_search (which you already have the include for) requires it to create the default color map. You can get around it by supplying a custom color map instead for that particular example, examples:
