From 2da3c1506f5ac60ac378f4d59ca19478db07cb7c Mon Sep 17 00:00:00 2001 From: Arnaud Becheler <8360330+Becheler@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:08:43 +0200 Subject: [PATCH] feature: add support for MAS stateful visitor --- .../utility/maximum_adjacency_search.cpp | 15 ++++----- ...maximum_adjacency_search_default_queue.cpp | 16 +++++----- .../utility/maximum_adjacency_search.adoc | 19 ++++++----- .../boost/graph/maximum_adjacency_search.hpp | 26 +++++++++++---- test/mas_test.cpp | 32 +++++++++++++++++++ 5 files changed, 76 insertions(+), 32 deletions(-) diff --git a/doc/modules/ROOT/examples/algorithms/utility/maximum_adjacency_search.cpp b/doc/modules/ROOT/examples/algorithms/utility/maximum_adjacency_search.cpp index 5bf8384ac..e70d14503 100644 --- a/doc/modules/ROOT/examples/algorithms/utility/maximum_adjacency_search.cpp +++ b/doc/modules/ROOT/examples/algorithms/utility/maximum_adjacency_search.cpp @@ -13,9 +13,8 @@ using vertex_descriptor = boost::graph_traits::vertex_descriptor; using weight_type = int; // records the vertices in the order the search visits them -struct order_recorder : boost::default_mas_visitor { - std::vector& order; - explicit order_recorder(std::vector& o) : order(o) {} +struct order_recorder : boost::graph::default_mas_visitor { + std::vector order; void finish_vertex(vertex_descriptor u, const Graph&) { order.push_back(u); } }; @@ -41,13 +40,13 @@ int main() { auto indices_map = boost::make_shared_array_property_map(boost::num_vertices(g), index_in_heap_type(-1), boost::get(boost::vertex_index, g)); max_priority_queue_type pq(distances_map, indices_map); - std::vector order; - order_recorder visitor(order); + order_recorder visitor; vertex_descriptor start = *boost::vertices(g).first; - boost::graph::maximum_adjacency_search(g, weight_map, visitor, start, pq); + // std::ref lets the visitor keep its state across the copy the algorithm makes + boost::graph::maximum_adjacency_search(g, weight_map, std::ref(visitor), start, pq); std::cout << "Visit order:"; - for (vertex_descriptor v : order) std::cout << ' ' << v; - std::cout << "\nLast visited vertex (highest connectivity): " << order.back() << '\n'; + for (vertex_descriptor v : visitor.order) std::cout << ' ' << v; + std::cout << "\nLast visited vertex (highest connectivity): " << visitor.order.back() << '\n'; } diff --git a/doc/modules/ROOT/examples/algorithms/utility/maximum_adjacency_search_default_queue.cpp b/doc/modules/ROOT/examples/algorithms/utility/maximum_adjacency_search_default_queue.cpp index 8c61cbc7e..5b6c57eb3 100644 --- a/doc/modules/ROOT/examples/algorithms/utility/maximum_adjacency_search_default_queue.cpp +++ b/doc/modules/ROOT/examples/algorithms/utility/maximum_adjacency_search_default_queue.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -9,9 +10,8 @@ using Graph = boost::adjacency_list::vertex_descriptor; // records the vertices in the order the search visits them -struct order_recorder : boost::default_mas_visitor { - std::vector& order; - explicit order_recorder(std::vector& o) : order(o) {} +struct order_recorder : boost::graph::default_mas_visitor { + std::vector order; void finish_vertex(vertex_descriptor u, const Graph&) { order.push_back(u); } }; @@ -26,12 +26,12 @@ int main() { auto weight_map = boost::get(&Edge::weight, g); - std::vector order; - order_recorder visitor(order); + order_recorder visitor; - boost::graph::maximum_adjacency_search(g, weight_map, visitor, *vertices(g).first); + // std::ref lets the visitor keep its state across the copy the algorithm makes + boost::graph::maximum_adjacency_search(g, weight_map, std::ref(visitor), *vertices(g).first); std::cout << "Visit order:"; - for (vertex_descriptor v : order) std::cout << ' ' << v; - std::cout << "\nLast visited vertex (highest connectivity): " << order.back() << '\n'; + for (vertex_descriptor v : visitor.order) std::cout << ' ' << v; + std::cout << "\nLast visited vertex (highest connectivity): " << visitor.order.back() << '\n'; } diff --git a/doc/modules/ROOT/pages/algorithms/utility/maximum_adjacency_search.adoc b/doc/modules/ROOT/pages/algorithms/utility/maximum_adjacency_search.adoc index 5fc801e32..842a66203 100644 --- a/doc/modules/ROOT/pages/algorithms/utility/maximum_adjacency_search.adoc +++ b/doc/modules/ROOT/pages/algorithms/utility/maximum_adjacency_search.adoc @@ -286,17 +286,21 @@ void maximum_adjacency_search( |=== +== Throws + +`bad_graph`:: If `num_vertices(g)` is less than 2. +`std::invalid_argument`:: If a max-priority queue is given as an argument and it is not empty. + == Visitor The `maximum_adjacency_search()` function invokes user-defined actions at four event points during the traversal. You supply these actions through a visitor object whose type models the <>. -Since the visitor parameter is passed by value, if the -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. +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. [WARNING] ==== @@ -444,11 +448,6 @@ struct my_mas_visitor }; ---- -== Throws - -`bad_graph`:: If `num_vertices(g)` is less than 2. -`std::invalid_argument`:: If a max-priority queue is given as an argument and it is not empty. - == References * David Matula (1993). "http://dl.acm.org/citation.cfm?id=313872&dl=ACM&coll=DL&CFID=85991501&CFTOKEN=44461131[A linear time 2 + epsilon approximation algorithm for edge connectivity]" diff --git a/include/boost/graph/maximum_adjacency_search.hpp b/include/boost/graph/maximum_adjacency_search.hpp index a37264e87..7dffc9ab7 100644 --- a/include/boost/graph/maximum_adjacency_search.hpp +++ b/include/boost/graph/maximum_adjacency_search.hpp @@ -2,6 +2,7 @@ //======================================================================= // Copyright 2012 Fernando Vilas // 2010 Daniel Trebbien +// 2026 Arnaud Becheler // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at @@ -122,6 +123,12 @@ using default_mas_visitor = mas_visitor<>; namespace mas_detail { +// Unwrap a visitor that may be passed with std::ref. +template < class Visitor > struct unwrap_visitor { using type = Visitor; }; +template < class Visitor > struct unwrap_visitor< std::reference_wrapper< Visitor > > { using type = Visitor; }; +template < class Visitor > Visitor& deref_visitor(Visitor& vis) { return vis; } +template < class Visitor > Visitor& deref_visitor(std::reference_wrapper< Visitor > vis) { return vis.get(); } + // Maximum adjacency sweep over an already populated queue. // Shared engine behind both maximum_adjacency_search and stoer_wagner_min_cut. // The graph may be contracted through assignments (each vertex maps to its representative) @@ -143,16 +150,19 @@ void mas_sweep( // reach counts are the queue keys. auto key_map = pq.keys(); + // resolve a std::ref-wrapped visitor to the referenced object + auto& vis_ref = deref_visitor(vis); + while (!pq.empty()) { // extract max: top then pop const auto u = pq.top(); - vis.start_vertex(u, g); + vis_ref.start_vertex(u, g); pq.pop(); for (const auto& e : make_iterator_range(out_edges(u, g))) { - vis.examine_edge(e, g); + vis_ref.examine_edge(e, g); // map the target to itself or its super node const auto v = get(assignment_map, target(e, g)); // in the queue means still unvisited @@ -173,7 +183,7 @@ void mas_sweep( for (const auto& e : make_iterator_range(out_edges(member, g))) { - vis.examine_edge(e, g); + vis_ref.examine_edge(e, g); // map the target to itself or its super node const auto v = get(assignment_map, target(e, g)); if (pq.contains(v)) @@ -183,7 +193,7 @@ void mas_sweep( } } } - vis.finish_vertex(u, g); + vis_ref.finish_vertex(u, g); } } } // namespace mas_detail @@ -208,7 +218,8 @@ void maximum_adjacency_search( BOOST_CONCEPT_ASSERT((boost::VertexListGraphConcept< Graph >)); BOOST_CONCEPT_ASSERT((boost::Convertible< directed_category, boost::undirected_tag >)); BOOST_CONCEPT_ASSERT((boost::ReadablePropertyMapConcept< WeightMap, edge_descriptor >)); - boost::function_requires< MASVisitorConcept< MASVisitor, Graph > >(); + using visitor_type = typename mas_detail::unwrap_visitor< MASVisitor >::type; + boost::function_requires< MASVisitorConcept< visitor_type, Graph > >(); BOOST_CONCEPT_ASSERT((boost::KeyedUpdatableQueueConcept< KeyedUpdatablePriorityQueue >)); if (num_vertices(g) < 2) @@ -219,11 +230,14 @@ void maximum_adjacency_search( // reach counts are the queue keys auto key_map = pq.keys(); + // resolve a std::ref-wrapped visitor to the referenced object + auto& vis_ref = mas_detail::deref_visitor(vis); + // seed every vertex with reach count 0 for (const auto& v : make_iterator_range(vertices(g))) { put(key_map, v, static_cast< weight_type >(0)); - vis.initialize_vertex(v, g); + vis_ref.initialize_vertex(v, g); pq.push(v); } BOOST_ASSERT(pq.size() >= 2); diff --git a/test/mas_test.cpp b/test/mas_test.cpp index 3e3cba503..a1d265eff 100644 --- a/test/mas_test.cpp +++ b/test/mas_test.cpp @@ -523,6 +523,37 @@ void test_exceptions() } } +// A stateful visitor that owns its state as a plain data member. +class counting_visitor : public boost::graph::default_mas_visitor +{ +public: + std::size_t finish_count = 0; + void finish_vertex(vertex_descriptor, const undirected_graph&) { ++finish_count; } +}; + +// A visitor passed with std::ref keeps its state across the algorithm +void test_stateful_visitor_with_ref() +{ + const undirected_graph g = mas_sw_oracle::make_weighted_graph(4, { { 0, 1, 1 }, { 1, 2, 1 }, { 2, 3, 1 } }); + auto weight_map = get(boost::edge_weight, g); + + // std::ref makes the algorithm operate on the caller's visitor + { + cv_maxheap_type pq = make_weighted_maxheap(g); + counting_visitor vis; + boost::graph::maximum_adjacency_search(g, weight_map, std::ref(vis), *vertices(g).first, pq); + BOOST_TEST_EQ(vis.finish_count, static_cast< std::size_t >(num_vertices(g))); + } + + // by value the caller's visitor is left unchanged + { + cv_maxheap_type pq = make_weighted_maxheap(g); + counting_visitor vis; + boost::graph::maximum_adjacency_search(g, weight_map, vis, *vertices(g).first, pq); + BOOST_TEST_EQ(vis.finish_count, static_cast< std::size_t >(0)); + } +} + int main() { test0(); @@ -537,6 +568,7 @@ int main() test9_weights_start_vertex(); test_maxflow_crossvalidation(); test_visitor_events(); + test_stateful_visitor_with_ref(); test_exceptions(); return boost::report_errors(); }