Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions doc/modules/ROOT/examples/algorithms/traversal/bfs.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/breadth_first_search.hpp>
#include <functional>
#include <iostream>
#include <vector>

struct VertexProps { int id; };

using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, VertexProps>;

struct DiscoverVisitor : boost::default_bfs_visitor {
void discover_vertex(Graph::vertex_descriptor v, const Graph& g) const {
std::cout << g[v].id << " ";
// records the vertices in the order the search discovers them
struct DiscoverRecorder : boost::default_bfs_visitor {
std::vector<int> order;
void discover_vertex(Graph::vertex_descriptor v, const Graph& g) {
order.push_back(g[v].id);
}
};

Expand All @@ -21,7 +24,12 @@ int main() {
boost::add_edge(1, 3, g);
boost::add_edge(2, 4, g);

DiscoverRecorder visitor;

// std::ref lets the visitor keep its state across the copy the algorithm makes
boost::breadth_first_search(g, 0, boost::visitor(std::ref(visitor)));

std::cout << "BFS discovery order: ";
boost::breadth_first_search(g, 0, boost::visitor(DiscoverVisitor{}));
for (int id : visitor.order) std::cout << id << " ";
std::cout << std::endl;
}
16 changes: 12 additions & 4 deletions doc/modules/ROOT/examples/algorithms/traversal/bfs_visit.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/breadth_first_search.hpp>
#include <boost/pending/queue.hpp>
#include <functional>
#include <iostream>
#include <vector>

Expand All @@ -9,9 +10,11 @@ struct VertexProps { int id; };
using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, VertexProps>;
using Vertex = boost::graph_traits<Graph>::vertex_descriptor;

struct PrintVisitor : boost::default_bfs_visitor {
void discover_vertex(Vertex v, const Graph& g) const {
std::cout << g[v].id << " ";
// records the vertices in the order the search visits them
struct OrderRecorder : boost::default_bfs_visitor {
std::vector<int> order;
void discover_vertex(Vertex v, const Graph& g) {
order.push_back(g[v].id);
}
};

Expand All @@ -29,7 +32,12 @@ int main() {
auto color_map = boost::make_iterator_property_map(colors.begin(), get(boost::vertex_index, g));
boost::queue<Vertex> q;

OrderRecorder visitor;

// std::ref lets the visitor keep its state across the copy the algorithm makes
boost::breadth_first_visit(g, vertex(0, g), q, std::ref(visitor), color_map);

std::cout << "BFS visit order: ";
boost::breadth_first_visit(g, vertex(0, g), q, PrintVisitor{}, color_map);
for (int id : visitor.order) std::cout << id << " ";
std::cout << std::endl;
}
22 changes: 12 additions & 10 deletions doc/modules/ROOT/examples/faq/visitor_by_value.cpp
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/breadth_first_search.hpp>
#include <functional>
#include <iostream>

using namespace boost;
using Graph = adjacency_list<vecS, vecS, undirectedS>;
using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS>;

struct Counter : default_bfs_visitor {
int* n; // indirection so mutations escape the copy
explicit Counter(int* p) : n(p) {}
struct Counter : boost::default_bfs_visitor {
int n = 0;
template <typename V, typename G>
void discover_vertex(V, const G&) const { ++*n; }
void discover_vertex(V, const G&) { ++n; }
};

int main() {
Graph g(4);
add_edge(0, 1, g); add_edge(1, 2, g); add_edge(2, 3, g);
boost::add_edge(0, 1, g); boost::add_edge(1, 2, g); boost::add_edge(2, 3, g);

int n = 0;
breadth_first_search(g, 0, visitor(Counter{&n}));
std::cout << "discovered " << n << " vertices\n";
Counter counter;

// std::ref lets the visitor keep its state across the copy the algorithm makes
boost::breadth_first_search(g, 0, boost::visitor(std::ref(counter)));

std::cout << "discovered " << counter.n << " vertices\n";
}
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,7 @@ the same example, except that the `adjacency_list` class used has
== Notes

[[notes]]
[1] Since the visitor parameter is passed by value, if your visitor
contains state then any changes to the state during the algorithm
will be made to a copy of the visitor object, not the visitor object
passed in. Therefore you may want the visitor to hold this state by
pointer or reference.
[1] The visitor is taken by value, so the algorithm works on a copy. To keep
state, give the visitor ordinary data members and pass it with `std::ref`.
The algorithm then operates on the referenced object and its
state survives the call.
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,7 @@ reaches a small portion of the graph.
== Notes

[[notes]]
[1] Since the visitor parameter is passed by value, if your visitor
contains state then any changes to the state during the algorithm
will be made to a copy of the visitor object, not the visitor object
passed in. Therefore you may want the visitor to hold this state by
pointer or reference.
[1] The visitor is taken by value, so the algorithm works on a copy. To keep
state, give the visitor ordinary data members and pass it with `std::ref`.
The algorithm then operates on the referenced object and its
state survives the call.
6 changes: 4 additions & 2 deletions doc/modules/ROOT/pages/faq.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,10 @@ include::example$faq/list_vertex_index.txt[]
Because a temporary cannot bind to a non-`const` reference, and BGL
wants to accept visitors constructed in-place at the call site. Any
mutable state inside the visitor therefore lives on the internal copy
and does not leak back out. Hold state by pointer or reference if you
need it to.
and does not leak back out. The depth-first, breadth-first and maximum
adjacency searches let you opt out: wrap the visitor in `std::ref` and
the algorithm operates on your own object. Elsewhere, hold state by
pointer or reference.

[source,cpp]
----
Expand Down
4 changes: 3 additions & 1 deletion doc/modules/ROOT/pages/visitors/overview.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ whose methods get called at these event points. This lets you inject custom
logic into an algorithm without modifying or rewriting it.

WARNING: Visitors are passed by value. If your visitor holds state (a counter,
a reference to a container), the algorithm operates on a copy. Hold state by
a reference to a container), the algorithm operates on a copy. The depth-first,
breadth-first and maximum adjacency searches take the visitor wrapped in
`std::ref`, which leaves the state on your own object. Elsewhere, hold state by
pointer or reference if you need to read it after the algorithm returns.

== Quick start: pre-built event visitors
Expand Down
28 changes: 17 additions & 11 deletions include/boost/graph/breadth_first_search.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <boost/pending/queue.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <boost/graph/detail/visitor_wrapper.hpp>
#include <boost/graph/visitors.hpp>
#include <boost/graph/named_function_params.hpp>
#include <boost/graph/overloading.hpp>
Expand Down Expand Up @@ -59,47 +60,50 @@ void breadth_first_visit(const IncidenceGraph& g, SourceIterator sources_begin,
BOOST_CONCEPT_ASSERT((IncidenceGraphConcept< IncidenceGraph >));
typedef graph_traits< IncidenceGraph > GTraits;
typedef typename GTraits::vertex_descriptor Vertex;
BOOST_CONCEPT_ASSERT((BFSVisitorConcept< BFSVisitor, IncidenceGraph >));
using visitor_type = ::boost::graph::detail::unwrap_visitor_t< BFSVisitor >;
BOOST_CONCEPT_ASSERT((BFSVisitorConcept< visitor_type, IncidenceGraph >));
BOOST_CONCEPT_ASSERT((ReadWritePropertyMapConcept< ColorMap, Vertex >));
typedef typename property_traits< ColorMap >::value_type ColorValue;
typedef color_traits< ColorValue > Color;
typename GTraits::out_edge_iterator ei, ei_end;

auto& vis_ref = ::boost::graph::detail::deref_visitor(vis);

for (; sources_begin != sources_end; ++sources_begin)
{
Vertex s = *sources_begin;
put(color, s, Color::gray());
vis.discover_vertex(s, g);
vis_ref.discover_vertex(s, g);
Q.push(s);
}
while (!Q.empty())
{
Vertex u = Q.top();
Q.pop();
vis.examine_vertex(u, g);
vis_ref.examine_vertex(u, g);
for (boost::tie(ei, ei_end) = out_edges(u, g); ei != ei_end; ++ei)
{
Vertex v = target(*ei, g);
vis.examine_edge(*ei, g);
vis_ref.examine_edge(*ei, g);
ColorValue v_color = get(color, v);
if (v_color == Color::white())
{
vis.tree_edge(*ei, g);
vis_ref.tree_edge(*ei, g);
put(color, v, Color::gray());
vis.discover_vertex(v, g);
vis_ref.discover_vertex(v, g);
Q.push(v);
}
else
{
vis.non_tree_edge(*ei, g);
vis_ref.non_tree_edge(*ei, g);
if (v_color == Color::gray())
vis.gray_target(*ei, g);
vis_ref.gray_target(*ei, g);
else
vis.black_target(*ei, g);
vis_ref.black_target(*ei, g);
}
} // end for
put(color, u, Color::black());
vis.finish_vertex(u, g);
vis_ref.finish_vertex(u, g);
} // end while
} // breadth_first_visit

Expand All @@ -125,11 +129,13 @@ void breadth_first_search(const VertexListGraph& g,
typedef typename property_traits< ColorMap >::value_type ColorValue;
typedef color_traits< ColorValue > Color;
typename boost::graph_traits< VertexListGraph >::vertex_iterator i, i_end;
auto& vis_ref = ::boost::graph::detail::deref_visitor(vis);
for (boost::tie(i, i_end) = vertices(g); i != i_end; ++i)
{
vis.initialize_vertex(*i, g);
vis_ref.initialize_vertex(*i, g);
put(color, *i, Color::white());
}
// breadth_first_visit takes the visitor by value, pass vis not vis_ref
breadth_first_visit(g, sources_begin, sources_end, Q, vis, color);
}

Expand Down
55 changes: 55 additions & 0 deletions test/bfs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/graph_archetypes.hpp>
#include <boost/graph/breadth_first_search.hpp>
#include <boost/pending/queue.hpp>

#include <boost/random/mersenne_twister.hpp>

#include <cstddef>
#include <functional>

#ifdef BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP
using namespace boost;
#endif
Expand Down Expand Up @@ -202,6 +206,54 @@ template < class Graph > struct bfs_test
}
};

// state in a plain data member, so it survives only through std::ref
struct discover_tally : boost::bfs_visitor<>
{
template < class Vertex, class Graph > void discover_vertex(Vertex, Graph&)
{
++count;
}
std::size_t count = 0;
};

void test_stateful_visitor_with_ref()
{
using graph_t = boost::adjacency_list< boost::vecS, boost::vecS,
boost::directedS,
boost::property< boost::vertex_color_t, boost::default_color_type > >;
graph_t g;
graph_t::vertex_descriptor a = boost::add_vertex(g);
graph_t::vertex_descriptor b = boost::add_vertex(g);
graph_t::vertex_descriptor c = boost::add_vertex(g);

// the search reaches one component only, every vertex is reachable from a
boost::add_edge(a, b, g);
boost::add_edge(b, c, g);
boost::add_edge(c, a, g);
boost::add_edge(a, c, g);

auto color_map = get(boost::vertex_color, g);

// breadth_first_visit runs first, it never initialises the colour map
discover_tally visit_vis;
boost::queue< graph_t::vertex_descriptor > visit_queue;
boost::breadth_first_visit(
g, a, visit_queue, std::ref(visit_vis), color_map);
BOOST_TEST_EQ(visit_vis.count, boost::num_vertices(g));

discover_tally search_vis;
boost::queue< graph_t::vertex_descriptor > search_queue;
boost::breadth_first_search(
g, a, search_queue, std::ref(search_vis), color_map);
BOOST_TEST_EQ(search_vis.count, boost::num_vertices(g));

// by value the caller's visitor is left untouched
discover_tally copied_vis;
boost::queue< graph_t::vertex_descriptor > copied_queue;
boost::breadth_first_search(g, a, copied_queue, copied_vis, color_map);
BOOST_TEST_EQ(copied_vis.count, static_cast< std::size_t >(0));
}

int main(int argc, char* argv[])
{
using namespace boost;
Expand All @@ -211,5 +263,8 @@ int main(int argc, char* argv[])

bfs_test< adjacency_list< vecS, vecS, directedS > >::go(max_V);
bfs_test< adjacency_list< vecS, vecS, undirectedS > >::go(max_V);

test_stateful_visitor_with_ref();

return boost::report_errors();
}
Loading