From 3953a03f22c955e5db701c1da7a3b0c7ca4640a1 Mon Sep 17 00:00:00 2001 From: Arnaud Becheler <8360330+Becheler@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:27:43 +0200 Subject: [PATCH] feature: stateful visitors --- .../clique_detection/bron_kerbosch.cpp | 29 +++++++++--- .../cycle_detection/tiernan_all_cycles.cpp | 30 ++++++++---- .../clique_detection/bron_kerbosch.adoc | 4 ++ .../cycle_detection/tiernan_all_cycles.adoc | 4 ++ .../algorithms/metrics/core_numbers.adoc | 2 +- .../shortest_paths/r_c_shortest_paths.adoc | 2 +- .../algorithms/utility/metric_tsp_approx.adoc | 2 +- .../boost/graph/bron_kerbosch_all_cliques.hpp | 11 +++-- include/boost/graph/core_numbers.hpp | 17 ++++--- include/boost/graph/hawick_circuits.hpp | 13 +----- include/boost/graph/metric_tsp_approx.hpp | 10 ++-- include/boost/graph/r_c_shortest_paths.hpp | 15 +++--- include/boost/graph/tiernan_all_cycles.hpp | 8 +++- test/bron_kerbosch_all_cliques.cpp | 46 +++++++++++++++++++ test/core_numbers_test.cpp | 45 +++++++++++++++++- test/metric_tsp_approx.cpp | 38 +++++++++++++++ test/r_c_shortest_paths_test.cpp | 43 +++++++++++++++++ test/tiernan_all_cycles.cpp | 46 +++++++++++++++++++ 18 files changed, 315 insertions(+), 50 deletions(-) diff --git a/doc/modules/ROOT/examples/algorithms/clique_detection/bron_kerbosch.cpp b/doc/modules/ROOT/examples/algorithms/clique_detection/bron_kerbosch.cpp index fb2e2a256..e56d948f8 100644 --- a/doc/modules/ROOT/examples/algorithms/clique_detection/bron_kerbosch.cpp +++ b/doc/modules/ROOT/examples/algorithms/clique_detection/bron_kerbosch.cpp @@ -6,17 +6,22 @@ #include #include #include +#include #include +#include +#include #include // Undirected graph with no properties using Graph = boost::adjacency_list; using Vertex = boost::graph_traits::vertex_descriptor; -// Custom visitor that prints each maximal clique found. +// Custom visitor that records each maximal clique found. // The clique is passed as a deque of vertex descriptors. -struct PrintCliquesVisitor +struct RecordCliquesVisitor { + std::vector cliques; + template void clique(const Clique& c, const G& /*g*/) { @@ -24,16 +29,18 @@ struct PrintCliquesVisitor std::vector sorted(c.begin(), c.end()); std::sort(sorted.begin(), sorted.end()); - std::cout << "Clique: {"; + std::ostringstream out; + out << "Clique: {"; for (std::size_t i = 0; i < sorted.size(); ++i) { if (i > 0) { - std::cout << ", "; + out << ", "; } - std::cout << sorted[i]; + out << sorted[i]; } - std::cout << "}" << std::endl; + out << "}"; + cliques.push_back(out.str()); } }; @@ -58,8 +65,16 @@ int main() boost::add_edge(1, 3, g); boost::add_edge(2, 3, g); + RecordCliquesVisitor visitor; + + // std::ref lets the visitor keep its state across the copy the algorithm makes + boost::bron_kerbosch_all_cliques(g, std::ref(visitor)); + std::cout << "Finding all maximal cliques:" << std::endl; - boost::bron_kerbosch_all_cliques(g, PrintCliquesVisitor{}); + for (const std::string& clique : visitor.cliques) + { + std::cout << clique << std::endl; + } return 0; } diff --git a/doc/modules/ROOT/examples/algorithms/cycle_detection/tiernan_all_cycles.cpp b/doc/modules/ROOT/examples/algorithms/cycle_detection/tiernan_all_cycles.cpp index c0f683647..980e2c13b 100644 --- a/doc/modules/ROOT/examples/algorithms/cycle_detection/tiernan_all_cycles.cpp +++ b/doc/modules/ROOT/examples/algorithms/cycle_detection/tiernan_all_cycles.cpp @@ -5,31 +5,37 @@ #include #include +#include #include +#include +#include #include // Directed graph with no bundled properties using Graph = boost::directed_graph<>; using Vertex = boost::graph_traits::vertex_descriptor; -// Custom visitor that prints each cycle found. +// Custom visitor that records each cycle found. // The cycle is passed as a const vector of vertex descriptors. -struct PrintCyclesVisitor +struct RecordCyclesVisitor { + std::vector cycles; + template void cycle(const Path& p, const G& g) { - std::cout << "Cycle: "; + std::ostringstream out; + out << "Cycle: "; for (std::size_t i = 0; i < p.size(); ++i) { if (i > 0) { - std::cout << " -> "; + out << " -> "; } - std::cout << boost::get(boost::vertex_index, g, p[i]); + out << boost::get(boost::vertex_index, g, p[i]); } - std::cout << " -> " << boost::get(boost::vertex_index, g, p.front()) - << std::endl; + out << " -> " << boost::get(boost::vertex_index, g, p.front()); + cycles.push_back(out.str()); } }; @@ -61,8 +67,16 @@ int main() g.add_edge(v1, v3); g.add_edge(v3, v1); + RecordCyclesVisitor visitor; + + // std::ref lets the visitor keep its state across the copy the algorithm makes + boost::tiernan_all_cycles(g, std::ref(visitor)); + std::cout << "Finding all elementary cycles:" << std::endl; - boost::tiernan_all_cycles(g, PrintCyclesVisitor{}); + for (const std::string& cycle : visitor.cycles) + { + std::cout << cycle << std::endl; + } return 0; } diff --git a/doc/modules/ROOT/pages/algorithms/clique_detection/bron_kerbosch.adoc b/doc/modules/ROOT/pages/algorithms/clique_detection/bron_kerbosch.adoc index aeca74607..944f0f9ab 100644 --- a/doc/modules/ROOT/pages/algorithms/clique_detection/bron_kerbosch.adoc +++ b/doc/modules/ROOT/pages/algorithms/clique_detection/bron_kerbosch.adoc @@ -47,6 +47,10 @@ void bron_kerbosch_all_cliques(const Graph& g, Visitor vis); | `Visitor vis` | Called with `vis.clique(clique, g)` for each maximal clique found. `clique` is a `std::deque`. + 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. | IN | `std::size_t min` diff --git a/doc/modules/ROOT/pages/algorithms/cycle_detection/tiernan_all_cycles.adoc b/doc/modules/ROOT/pages/algorithms/cycle_detection/tiernan_all_cycles.adoc index ddede6108..4d5fbe73a 100644 --- a/doc/modules/ROOT/pages/algorithms/cycle_detection/tiernan_all_cycles.adoc +++ b/doc/modules/ROOT/pages/algorithms/cycle_detection/tiernan_all_cycles.adoc @@ -42,6 +42,10 @@ void tiernan_all_cycles(const Graph& g, Visitor vis, | `Visitor vis` | Called with `vis.cycle(path, g)` for each cycle found. `path` is a `const std::vector&`. + 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. | IN | `std::size_t minlen` diff --git a/doc/modules/ROOT/pages/algorithms/metrics/core_numbers.adoc b/doc/modules/ROOT/pages/algorithms/metrics/core_numbers.adoc index 16ad2d89c..1139f84be 100644 --- a/doc/modules/ROOT/pages/algorithms/metrics/core_numbers.adoc +++ b/doc/modules/ROOT/pages/algorithms/metrics/core_numbers.adoc @@ -127,7 +127,7 @@ The visitor type used internally. Model of `CoreNumbersVisitorConcept`. Derives | IN | `CoreNumVisitor vis` -| Model of `CoreNumbersVisitorConcept`. Typically constructed via `make_core_numbers_visitor`. +| Model of `CoreNumbersVisitorConcept`. Typically constructed via `make_core_numbers_visitor`. 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. |=== The function returns the largest core number assigned. diff --git a/doc/modules/ROOT/pages/algorithms/shortest_paths/r_c_shortest_paths.adoc b/doc/modules/ROOT/pages/algorithms/shortest_paths/r_c_shortest_paths.adoc index 8fa627784..a5529208a 100644 --- a/doc/modules/ROOT/pages/algorithms/shortest_paths/r_c_shortest_paths.adoc +++ b/doc/modules/ROOT/pages/algorithms/shortest_paths/r_c_shortest_paths.adoc @@ -639,7 +639,7 @@ A type modelling the DominanceFunction concept must return `true` if and only if [#ResourceConstrainedShortestPathsVisitor] == ResourceConstrainedShortestPathsVisitor -This concept defines the visitor interface for `r_c_shortest_paths`. A user can define a type with this interface and pass an object of this type to `r_c_shortest_paths` to perform user-defined actions at the event points of the algorithm. Note that the object is passed by value. +This concept defines the visitor interface for `r_c_shortest_paths`. A user can define a type with this interface and pass an object of this type to `r_c_shortest_paths` to perform user-defined actions at the event points of the algorithm. 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. *Refinement of* + http://www.boost.org/sgi/stl/DefaultConstructible.html[DefaultConstructible], diff --git a/doc/modules/ROOT/pages/algorithms/utility/metric_tsp_approx.adoc b/doc/modules/ROOT/pages/algorithms/utility/metric_tsp_approx.adoc index 433a8c55a..c45756da5 100644 --- a/doc/modules/ROOT/pages/algorithms/utility/metric_tsp_approx.adoc +++ b/doc/modules/ROOT/pages/algorithms/utility/metric_tsp_approx.adoc @@ -363,6 +363,6 @@ The overloads that do not accept a `start` vertex use `*vertices(g).first` as th == Notes -[#1]#[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]#[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. [#2]#[2]# Passing an `adjacency_list` with a vertex _not_ set selected by `vecS` will result in _O(n^2^)_ performance. diff --git a/include/boost/graph/bron_kerbosch_all_cliques.hpp b/include/boost/graph/bron_kerbosch_all_cliques.hpp index 28bc10f28..3a56f73e3 100644 --- a/include/boost/graph/bron_kerbosch_all_cliques.hpp +++ b/include/boost/graph/bron_kerbosch_all_cliques.hpp @@ -13,6 +13,7 @@ #include +#include #include #include @@ -168,9 +169,12 @@ namespace detail Container& nots, Visitor vis, std::size_t min) { BOOST_CONCEPT_ASSERT((GraphConcept< Graph >)); - BOOST_CONCEPT_ASSERT((CliqueVisitorConcept< Visitor, Clique, Graph >)); + using visitor_type = ::boost::graph::detail::unwrap_visitor_t< Visitor >; + BOOST_CONCEPT_ASSERT((CliqueVisitorConcept< visitor_type, Clique, Graph >)); typedef typename graph_traits< Graph >::vertex_descriptor Vertex; + auto& vis_ref = ::boost::graph::detail::deref_visitor(vis); + // Is there vertex in nots that is connected to all vertices // in the candidate set? If so, no clique can ever be found. // This could be broken out into a separate function. @@ -248,7 +252,7 @@ namespace detail // the clique is below our radar, then we won't visit it. if (clique.size() >= min) { - vis.clique(clique, g); + vis_ref.clique(clique, g); } } else @@ -277,7 +281,8 @@ inline void bron_kerbosch_all_cliques( typedef typename graph_traits< Graph >::vertex_iterator VertexIterator; typedef std::vector< Vertex > VertexSet; typedef std::deque< Vertex > Clique; - BOOST_CONCEPT_ASSERT((CliqueVisitorConcept< Visitor, Clique, Graph >)); + using visitor_type = ::boost::graph::detail::unwrap_visitor_t< Visitor >; + BOOST_CONCEPT_ASSERT((CliqueVisitorConcept< visitor_type, Clique, Graph >)); // NOTE: We're using a deque to implement the clique, because it provides // constant inserts and removals at the end and also a constant size. diff --git a/include/boost/graph/core_numbers.hpp b/include/boost/graph/core_numbers.hpp index 95053caf1..9a461cceb 100644 --- a/include/boost/graph/core_numbers.hpp +++ b/include/boost/graph/core_numbers.hpp @@ -12,6 +12,7 @@ #define BOOST_GRAPH_CORE_NUMBERS_HPP #include +#include #include #include #include @@ -149,6 +150,8 @@ namespace detail typename property_traits< CoreMap >::value_type core_numbers_impl( Graph& g, CoreMap c, EdgeWeightMap wm, MutableQueue& Q, Visitor vis) { + auto& vis_ref = ::boost::graph::detail::deref_visitor(vis); + typename property_traits< CoreMap >::value_type v_cn = 0; typedef typename graph_traits< Graph >::vertex_descriptor vertex; while (!Q.empty()) @@ -156,13 +159,13 @@ namespace detail // remove v from the Q, and then decrease the core numbers // of its successors vertex v = Q.top(); - vis.examine_vertex(v, g); + vis_ref.examine_vertex(v, g); Q.pop(); v_cn = get(c, v); typename graph_traits< Graph >::out_edge_iterator oi, oi_end; for (boost::tie(oi, oi_end) = out_edges(v, g); oi != oi_end; ++oi) { - vis.examine_edge(*oi, g); + vis_ref.examine_edge(*oi, g); vertex u = target(*oi, g); // if c[u] > c[v], then u is still in the graph, if (get(c, u) > v_cn) @@ -173,7 +176,7 @@ namespace detail Q.update(u); } } - vis.finish_vertex(v, g); + vis_ref.finish_vertex(v, g); } return (v_cn); } @@ -213,6 +216,8 @@ namespace detail typename property_traits< CoreMap >::value_type core_numbers_impl( Graph& g, CoreMap c, PositionMap pos, Visitor vis) { + auto& vis_ref = ::boost::graph::detail::deref_visitor(vis); + typedef typename graph_traits< Graph >::vertices_size_type size_type; typedef typename graph_traits< Graph >::degree_size_type degree_type; typedef typename graph_traits< Graph >::vertex_descriptor vertex; @@ -268,12 +273,12 @@ namespace detail for (size_type i = 0; i < num_vertices(g); ++i) { vertex v = vert[i]; - vis.examine_vertex(v, g); + vis_ref.examine_vertex(v, g); v_cn = get(c, v); typename graph_traits< Graph >::out_edge_iterator oi, oi_end; for (boost::tie(oi, oi_end) = out_edges(v, g); oi != oi_end; ++oi) { - vis.examine_edge(*oi, g); + vis_ref.examine_edge(*oi, g); vertex u = target(*oi, g); // if c[u] > c[v], then u is still in the graph, if (get(c, u) > v_cn) @@ -303,7 +308,7 @@ namespace detail put(c, u, get(c, u) - 1); } } - vis.finish_vertex(v, g); + vis_ref.finish_vertex(v, g); } return v_cn; } diff --git a/include/boost/graph/hawick_circuits.hpp b/include/boost/graph/hawick_circuits.hpp index a26f7b8ba..24b9423da 100644 --- a/include/boost/graph/hawick_circuits.hpp +++ b/include/boost/graph/hawick_circuits.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -89,16 +90,6 @@ namespace hawick_circuits_detail return std::find(boost::begin(c), boost::end(c), v) != boost::end(c); } - template < typename T > - struct unwrap_reference_wrapper { - typedef T type; - }; - - template < typename T > - struct unwrap_reference_wrapper > { - typedef T& type; - }; - /*! * @internal * Algorithm finding all the cycles starting from a given vertex. @@ -324,7 +315,7 @@ namespace hawick_circuits_detail typedef std::vector< Vertex > Stack; typedef std::vector< std::vector< Vertex > > ClosedMatrix; - typedef typename unwrap_reference_wrapper::type VisitorNoRef; + using VisitorNoRef = ::boost::graph::detail::unwrap_visitor_t< Visitor >; typedef hawick_circuits_from< Graph, VisitorNoRef, VertexIndexMap, Stack, ClosedMatrix, GetAdjacentVertices > diff --git a/include/boost/graph/metric_tsp_approx.hpp b/include/boost/graph/metric_tsp_approx.hpp index 77bab72ca..ec60948b8 100644 --- a/include/boost/graph/metric_tsp_approx.hpp +++ b/include/boost/graph/metric_tsp_approx.hpp @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -158,8 +159,11 @@ void metric_tsp_approx_from_vertex(const VertexListGraph& g, using namespace std; BOOST_CONCEPT_ASSERT((VertexListGraphConcept< VertexListGraph >)); + using visitor_type = ::boost::graph::detail::unwrap_visitor_t< TSPVertexVisitor >; BOOST_CONCEPT_ASSERT( - (TSPVertexVisitorConcept< TSPVertexVisitor, VertexListGraph >)); + (TSPVertexVisitorConcept< visitor_type, VertexListGraph >)); + + auto& vis_ref = ::boost::graph::detail::deref_visitor(vis); // Types related to the input graph (GVertex is a template parameter). typedef typename graph_traits< VertexListGraph >::vertex_descriptor GVertex; @@ -220,11 +224,11 @@ void metric_tsp_approx_from_vertex(const VertexListGraph& g, { // TODO: This is will be O(n^2) if vertex storage of g != vecS. GVertex v = *next(g_verts.first, get(vertex_index, mst)[*curr]); - vis.visit_vertex(v, g); + vis_ref.visit_vertex(v, g); } // Connect back to the start of the tour - vis.visit_vertex(start, g); + vis_ref.visit_vertex(start, g); } // Default tsp tour visitor that puts the tour in an OutputIterator diff --git a/include/boost/graph/r_c_shortest_paths.hpp b/include/boost/graph/r_c_shortest_paths.hpp index 0f8040a2a..9c4ffac9f 100644 --- a/include/boost/graph/r_c_shortest_paths.hpp +++ b/include/boost/graph/r_c_shortest_paths.hpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -149,6 +150,8 @@ namespace detail // to specify the memory management strategy for the labels LabelAllocator /*la*/, Visitor vis) { + auto& vis_ref = ::boost::graph::detail::deref_visitor(vis); + using edge_descriptor = typename graph_traits< Graph >::edge_descriptor; pareto_optimal_resource_containers.clear(); @@ -212,11 +215,11 @@ namespace detail auto b_feasible = true; while (!unprocessed_labels.empty() - && vis.on_enter_loop(unprocessed_labels, g)) + && vis_ref.on_enter_loop(unprocessed_labels, g)) { sp_label_type cur_label = unprocessed_labels.top(); unprocessed_labels.pop(); - vis.on_label_popped(*cur_label, g); + vis_ref.on_label_popped(*cur_label, g); // an Splabel object in unprocessed_labels and the respective // Splabel object in the respective list of // vec_vertex_labels share their embedded r_c_shortest_paths_label @@ -346,7 +349,7 @@ namespace detail if (!cur_label->b_is_dominated) { cur_label->b_is_processed = true; - vis.on_label_not_dominated(*cur_label, g); + vis_ref.on_label_not_dominated(*cur_label, g); auto cur_vertex = cur_label->resident_vertex; typename graph_traits< Graph >::out_edge_iterator oei, oei_end; for (boost::tie(oei, oei_end) = out_edges(cur_vertex, g); @@ -365,12 +368,12 @@ namespace detail if (!b_feasible) { - vis.on_label_not_feasible(*new_label, g); + vis_ref.on_label_not_feasible(*new_label, g); new_label.reset(); } else { - vis.on_label_feasible(*new_label, g); + vis_ref.on_label_feasible(*new_label, g); vec_vertex_labels[new_label->resident_vertex].push_back( new_label); unprocessed_labels.push(new_label); @@ -379,7 +382,7 @@ namespace detail } else { - vis.on_label_dominated(*cur_label, g); + vis_ref.on_label_dominated(*cur_label, g); cur_label.reset(); } } diff --git a/include/boost/graph/tiernan_all_cycles.hpp b/include/boost/graph/tiernan_all_cycles.hpp index c24c87553..792681b93 100644 --- a/include/boost/graph/tiernan_all_cycles.hpp +++ b/include/boost/graph/tiernan_all_cycles.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -260,10 +261,13 @@ namespace detail BOOST_CONCEPT_ASSERT((VertexListGraphConcept< Graph >)); typedef typename graph_traits< Graph >::vertex_descriptor Vertex; typedef std::vector< Vertex > Path; - BOOST_CONCEPT_ASSERT((CycleVisitorConcept< Visitor, Path, Graph >)); + using visitor_type = ::boost::graph::detail::unwrap_visitor_t< Visitor >; + BOOST_CONCEPT_ASSERT((CycleVisitorConcept< visitor_type, Path, Graph >)); typedef std::vector< Vertex > VertexList; typedef std::vector< VertexList > ClosedMatrix; + auto& vis_ref = ::boost::graph::detail::deref_visitor(vis); + Path p; ClosedMatrix closed(num_vertices(g), VertexList()); Vertex null = graph_traits< Graph >::null_vertex(); @@ -285,7 +289,7 @@ namespace detail // a cycle. if (detail::can_wrap_path(g, p) && p.size() >= minlen) { - vis.cycle(p, g); + vis_ref.cycle(p, g); } if (!detail::exhaust_paths(g, p, closed)) diff --git a/test/bron_kerbosch_all_cliques.cpp b/test/bron_kerbosch_all_cliques.cpp index 0a0967dd1..61d4df6cc 100644 --- a/test/bron_kerbosch_all_cliques.cpp +++ b/test/bron_kerbosch_all_cliques.cpp @@ -18,6 +18,11 @@ #include +#include + +#include +#include + using namespace std; using namespace boost; @@ -64,6 +69,43 @@ template < typename Graph > void test() bron_kerbosch_all_cliques(g, clique_validator()); } +// state in a plain data member, so it survives only through std::ref +struct clique_tally +{ + template < typename Clique, typename Graph > + void clique(const Clique&, const Graph&) + { + ++count; + } + std::size_t count = 0; +}; + +void test_stateful_visitor_with_ref() +{ + using graph_t = boost::undirected_graph<>; + graph_t g; + graph_t::vertex_descriptor v0 = g.add_vertex(); + graph_t::vertex_descriptor v1 = g.add_vertex(); + graph_t::vertex_descriptor v2 = g.add_vertex(); + graph_t::vertex_descriptor v3 = g.add_vertex(); + + // two triangles sharing an edge, so two maximal cliques + g.add_edge(v0, v1); + g.add_edge(v0, v2); + g.add_edge(v1, v2); + g.add_edge(v1, v3); + g.add_edge(v2, v3); + + clique_tally tracked; + boost::bron_kerbosch_all_cliques(g, std::ref(tracked)); + BOOST_TEST_EQ(tracked.count, static_cast< std::size_t >(2)); + + // by value the caller's visitor is left untouched + clique_tally copied; + boost::bron_kerbosch_all_cliques(g, copied); + BOOST_TEST_EQ(copied.count, static_cast< std::size_t >(0)); +} + int main(int, char*[]) { typedef undirected_graph<> Graph; @@ -74,4 +116,8 @@ int main(int, char*[]) std::cout << "*** directed ***\n"; test< DiGraph >(); + + test_stateful_visitor_with_ref(); + + return boost::report_errors(); } diff --git a/test/core_numbers_test.cpp b/test/core_numbers_test.cpp index 24c4e1120..3855de9bd 100644 --- a/test/core_numbers_test.cpp +++ b/test/core_numbers_test.cpp @@ -8,6 +8,10 @@ #include #include #include +#include + +#include +#include #include using namespace boost; @@ -140,6 +144,43 @@ int test_3() return 0; } +// state in a plain data member, so it survives only through std::ref +struct examine_tally : boost::core_numbers_visitor<> +{ + template < typename Vertex, typename Graph > + void examine_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::undirectedS >; + + // a triangle with a pendant vertex, every vertex is examined once + graph_t g(4); + boost::add_edge(0, 1, g); + boost::add_edge(1, 2, g); + boost::add_edge(2, 0, g); + boost::add_edge(2, 3, g); + + std::vector< int > core(boost::num_vertices(g)); + auto core_map = boost::make_iterator_property_map( + core.begin(), boost::get(boost::vertex_index, g)); + + examine_tally tracked; + boost::core_numbers(g, core_map, std::ref(tracked)); + BOOST_TEST_EQ(tracked.count, boost::num_vertices(g)); + + // by value the caller's visitor is left untouched + examine_tally copied; + boost::core_numbers(g, core_map, copied); + BOOST_TEST_EQ(copied.count, static_cast< std::size_t >(0)); +} + int main(int, char**) { int nfail = 0, ntotal = 0; @@ -190,5 +231,7 @@ int main(int, char**) printf("Total tests : %3i\n", ntotal); printf("Total failed : %3i\n", nfail); - return nfail != 0; + test_stateful_visitor_with_ref(); + + return (boost::report_errors() != 0) || (nfail != 0); } diff --git a/test/metric_tsp_approx.cpp b/test/metric_tsp_approx.cpp index 9d7b03d52..ee2d24437 100644 --- a/test/metric_tsp_approx.cpp +++ b/test/metric_tsp_approx.cpp @@ -22,6 +22,9 @@ #include #include +#include +#include + template < typename PointType > struct cmpPnt { bool operator()(const boost::simple_point< PointType >& l, @@ -169,6 +172,39 @@ template < typename PositionVec > void checkAdjList(PositionVec v) c.clear(); } +// state in a plain data member, so it survives only through std::ref +struct visit_tally +{ + template < typename Vertex, typename Graph > + void visit_vertex(Vertex, const Graph&) + { + ++count; + } + std::size_t count = 0; +}; + +void test_stateful_visitor_with_ref() +{ + using graph_t = boost::adjacency_list< boost::vecS, boost::vecS, + boost::undirectedS, boost::no_property, + boost::property< boost::edge_weight_t, int > >; + + // a unit triangle, the tour visits every vertex then returns to the start + graph_t g(3); + boost::add_edge(0, 1, 1, g); + boost::add_edge(1, 2, 1, g); + boost::add_edge(0, 2, 1, g); + + visit_tally tracked; + boost::metric_tsp_approx(g, std::ref(tracked)); + BOOST_TEST_EQ(tracked.count, boost::num_vertices(g) + 1); + + // by value the caller's visitor is left untouched + visit_tally copied; + boost::metric_tsp_approx(g, copied); + BOOST_TEST_EQ(copied.count, static_cast< std::size_t >(0)); +} + int main() { using namespace boost; @@ -285,5 +321,7 @@ int main() } BOOST_TEST(caught); + test_stateful_visitor_with_ref(); + return boost::report_errors(); } diff --git a/test/r_c_shortest_paths_test.cpp b/test/r_c_shortest_paths_test.cpp index e1b805cb6..4ed263eb3 100644 --- a/test/r_c_shortest_paths_test.cpp +++ b/test/r_c_shortest_paths_test.cpp @@ -16,6 +16,9 @@ #include #include +#include +#include + using namespace boost; struct SPPRC_Example_Graph_Vert_Prop @@ -287,6 +290,17 @@ class dominance_spptw_marked } }; +// state in a plain data member, so it survives only through std::ref +struct popped_tally : boost::default_r_c_shortest_paths_visitor +{ + template < class Label, class Graph > + void on_label_popped(const Label&, const Graph&) + { + ++count; + } + std::size_t count = 0; +}; + int main(int, char*[]) { SPPRC_Example_Graph g; @@ -692,6 +706,35 @@ int main(int, char*[]) BOOST_TEST(pareto_opt_rc.cost == 3); + // the same run again, with a visitor that keeps its own label count + std::vector< graph_traits< SPPRC_Example_Graph >::edge_descriptor > + tracked_solution; + spp_spptw_res_cont tracked_rc; + popped_tally tracked; + r_c_shortest_paths(g2, get(&SPPRC_Example_Graph_Vert_Prop::num, g2), 0, 3, + tracked_solution, tracked_rc, spp_spptw_res_cont(0, 0), ref_spptw(), + dominance_spptw(), + std::allocator< r_c_shortest_paths_label< SPPRC_Example_Graph, + spp_spptw_res_cont > >(), + std::ref(tracked)); + + BOOST_TEST(tracked_rc.cost == 3); + BOOST_TEST(tracked.count > 0); + + // by value the caller's visitor is left untouched + std::vector< graph_traits< SPPRC_Example_Graph >::edge_descriptor > + copied_solution; + spp_spptw_res_cont copied_rc; + popped_tally copied; + r_c_shortest_paths(g2, get(&SPPRC_Example_Graph_Vert_Prop::num, g2), 0, 3, + copied_solution, copied_rc, spp_spptw_res_cont(0, 0), ref_spptw(), + dominance_spptw(), + std::allocator< r_c_shortest_paths_label< SPPRC_Example_Graph, + spp_spptw_res_cont > >(), + copied); + + BOOST_TEST_EQ(copied.count, static_cast< std::size_t >(0)); + SPPRC_Example_Graph g3; add_vertex(SPPRC_Example_Graph_Vert_Prop(0, 0, 1000), g3); add_vertex(SPPRC_Example_Graph_Vert_Prop(1, 0, 1000), g3); diff --git a/test/tiernan_all_cycles.cpp b/test/tiernan_all_cycles.cpp index 41431cd8b..3abe0d84f 100644 --- a/test/tiernan_all_cycles.cpp +++ b/test/tiernan_all_cycles.cpp @@ -14,6 +14,11 @@ #include +#include + +#include +#include + using namespace std; using namespace boost; @@ -61,6 +66,43 @@ template < typename Graph > void test() cout << "# cycles: " << vis.cycles << "\n"; } +// state in a plain data member, so it survives only through std::ref +struct cycle_tally +{ + template < typename Path, typename Graph > + void cycle(const Path&, const Graph&) + { + ++count; + } + std::size_t count = 0; +}; + +void test_stateful_visitor_with_ref() +{ + using graph_t = boost::directed_graph<>; + graph_t g; + graph_t::vertex_descriptor v0 = g.add_vertex(); + graph_t::vertex_descriptor v1 = g.add_vertex(); + graph_t::vertex_descriptor v2 = g.add_vertex(); + graph_t::vertex_descriptor v3 = g.add_vertex(); + + // a triangle and a two cycle, so two elementary cycles + g.add_edge(v0, v1); + g.add_edge(v1, v2); + g.add_edge(v2, v0); + g.add_edge(v1, v3); + g.add_edge(v3, v1); + + cycle_tally tracked; + boost::tiernan_all_cycles(g, std::ref(tracked)); + BOOST_TEST_EQ(tracked.count, static_cast< std::size_t >(2)); + + // by value the caller's visitor is left untouched + cycle_tally copied; + boost::tiernan_all_cycles(g, copied); + BOOST_TEST_EQ(copied.count, static_cast< std::size_t >(0)); +} + int main(int, char*[]) { typedef undirected_graph<> Graph; @@ -71,4 +113,8 @@ int main(int, char*[]) std::cout << "*** directed ***\n"; test< DiGraph >(); + + test_stateful_visitor_with_ref(); + + return boost::report_errors(); }