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
20 changes: 15 additions & 5 deletions doc/modules/ROOT/examples/algorithms/traversal/dfs.cpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/depth_first_search.hpp>
#include <functional>
#include <iostream>
#include <string>
#include <vector>

struct VertexProps { int id; };

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

// records the events in the order the search reaches them
struct DFSVisitor : boost::default_dfs_visitor {
void discover_vertex(Graph::vertex_descriptor v, const Graph& g) const {
std::cout << "discover " << g[v].id << "\n";
std::vector<std::string> events;
void discover_vertex(Graph::vertex_descriptor v, const Graph& g) {
events.push_back("discover " + std::to_string(g[v].id));
}
void finish_vertex(Graph::vertex_descriptor v, const Graph& g) const {
std::cout << "finish " << g[v].id << "\n";
void finish_vertex(Graph::vertex_descriptor v, const Graph& g) {
events.push_back("finish " + std::to_string(g[v].id));
}
};

Expand All @@ -22,5 +27,10 @@ int main() {
boost::add_edge(0, 2, g);
boost::add_edge(1, 3, g);

boost::depth_first_search(g, boost::visitor(DFSVisitor{}));
DFSVisitor visitor;

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

for (const std::string& event : visitor.events) std::cout << event << "\n";
}
16 changes: 12 additions & 4 deletions doc/modules/ROOT/examples/algorithms/traversal/dfs_visit.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/depth_first_search.hpp>
#include <functional>
#include <iostream>
#include <vector>

Expand All @@ -8,9 +9,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_dfs_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_dfs_visitor {
std::vector<int> order;
void discover_vertex(Vertex v, const Graph& g) {
order.push_back(g[v].id);
}
};

Expand All @@ -27,7 +30,12 @@ int main() {
std::vector<boost::default_color_type> colors(num_vertices(g), boost::white_color);
auto color_map = boost::make_iterator_property_map(colors.begin(), get(boost::vertex_index, g));

OrderRecorder visitor;

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

std::cout << "DFS visit order: ";
boost::depth_first_visit(g, vertex(0, g), PrintVisitor{}, color_map);
for (int id : visitor.order) std::cout << id << " ";
std::cout << std::endl;
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/undirected_dfs.hpp>
#include <functional>
#include <iostream>
#include <string>
#include <vector>

struct VertexProps { int id; };

using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, VertexProps>;
using Edge = boost::graph_traits<Graph>::edge_descriptor;

// records the events in the order the search reaches them
struct Visitor : boost::default_dfs_visitor {
void discover_vertex(Graph::vertex_descriptor v, const Graph& g) const {
std::cout << "discover " << g[v].id << "\n";
std::vector<std::string> events;
void discover_vertex(Graph::vertex_descriptor v, const Graph& g) {
events.push_back("discover " + std::to_string(g[v].id));
}
void finish_vertex(Graph::vertex_descriptor v, const Graph& g) const {
std::cout << "finish " << g[v].id << "\n";
void finish_vertex(Graph::vertex_descriptor v, const Graph& g) {
events.push_back("finish " + std::to_string(g[v].id));
}
};

Expand All @@ -27,7 +32,12 @@ int main() {
using EdgeColorMap = std::map<Edge, boost::default_color_type>;
ColorMap vcmap;
EdgeColorMap ecmap;
boost::undirected_dfs(g, Visitor{},
Visitor visitor;

// std::ref lets the visitor keep its state across the copy the algorithm makes
boost::undirected_dfs(g, std::ref(visitor),
boost::make_assoc_property_map(vcmap),
boost::make_assoc_property_map(ecmap));

for (const std::string& event : visitor.events) std::cout << event << "\n";
}
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,7 @@ xref:about/graph_theory_review.adoc#fig:dfs-example[Figure 1].
== 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 @@ -125,8 +125,7 @@ another marked vertices.
== 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 @@ -323,8 +323,7 @@ finish vertex u
== 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.
28 changes: 18 additions & 10 deletions include/boost/graph/depth_first_search.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <boost/graph/properties.hpp>
#include <boost/graph/visitors.hpp>
#include <boost/graph/named_function_params.hpp>
#include <boost/graph/detail/visitor_wrapper.hpp>
#include <boost/ref.hpp>
#include <boost/optional.hpp>
#include <boost/concept/assert.hpp>
Expand Down Expand Up @@ -101,6 +102,8 @@ namespace detail
template < typename E, typename G, typename Vis >
void call_finish_edge(Vis& vis, E e, const G& g)
{ // Only call if the visitor has a callable finish_edge(e, g)
static_assert(!::boost::graph::detail::is_reference_wrapper_v< Vis >,
"unwrap the visitor before probing finish_edge");
do_call_finish_edge< has_finish_edge< Vis, E, G >::value >::
call_finish_edge(vis, e, g);
}
Expand Down Expand Up @@ -269,23 +272,26 @@ void depth_first_search(const VertexListGraph& g, DFSVisitor vis,
typename graph_traits< VertexListGraph >::vertex_descriptor start_vertex)
{
typedef typename graph_traits< VertexListGraph >::vertex_descriptor Vertex;
BOOST_CONCEPT_ASSERT((DFSVisitorConcept< DFSVisitor, VertexListGraph >));
using visitor_type = ::boost::graph::detail::unwrap_visitor_t< DFSVisitor >;
BOOST_CONCEPT_ASSERT((DFSVisitorConcept< visitor_type, VertexListGraph >));
typedef typename property_traits< ColorMap >::value_type ColorValue;
typedef color_traits< ColorValue > Color;

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

typename graph_traits< VertexListGraph >::vertex_iterator ui, ui_end;
for (boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui)
{
Vertex u = *ui;
put(color, u, Color::white());
vis.initialize_vertex(u, g);
vis_ref.initialize_vertex(u, g);
}

if (start_vertex != detail::get_default_starting_vertex(g))
{
vis.start_vertex(start_vertex, g);
vis_ref.start_vertex(start_vertex, g);
detail::depth_first_visit_impl(
g, start_vertex, vis, color, detail::nontruth2());
g, start_vertex, vis_ref, color, detail::nontruth2());
}

for (boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui)
Expand All @@ -294,9 +300,9 @@ void depth_first_search(const VertexListGraph& g, DFSVisitor vis,
ColorValue u_color = get(color, u);
if (u_color == Color::white())
{
vis.start_vertex(u, g);
vis_ref.start_vertex(u, g);
detail::depth_first_visit_impl(
g, u, vis, color, detail::nontruth2());
g, u, vis_ref, color, detail::nontruth2());
}
}
}
Expand Down Expand Up @@ -404,8 +410,9 @@ void depth_first_visit(const IncidenceGraph& g,
typename graph_traits< IncidenceGraph >::vertex_descriptor u,
DFSVisitor vis, ColorMap color)
{
vis.start_vertex(u, g);
detail::depth_first_visit_impl(g, u, vis, color, detail::nontruth2());
auto& vis_ref = ::boost::graph::detail::deref_visitor(vis);
vis_ref.start_vertex(u, g);
detail::depth_first_visit_impl(g, u, vis_ref, color, detail::nontruth2());
}

template < class IncidenceGraph, class DFSVisitor, class ColorMap,
Expand All @@ -414,8 +421,9 @@ void depth_first_visit(const IncidenceGraph& g,
typename graph_traits< IncidenceGraph >::vertex_descriptor u,
DFSVisitor vis, ColorMap color, TerminatorFunc func = TerminatorFunc())
{
vis.start_vertex(u, g);
detail::depth_first_visit_impl(g, u, vis, color, func);
auto& vis_ref = ::boost::graph::detail::deref_visitor(vis);
vis_ref.start_vertex(u, g);
detail::depth_first_visit_impl(g, u, vis_ref, color, func);
}
} // namespace boost

Expand Down
75 changes: 75 additions & 0 deletions include/boost/graph/detail/visitor_wrapper.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//=======================================================================
// Copyright (C) 2026 Arnaud Becheler
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================

#ifndef BOOST_GRAPH_DETAIL_VISITOR_WRAPPER_HPP
#define BOOST_GRAPH_DETAIL_VISITOR_WRAPPER_HPP

#include <functional>
#include <type_traits>

namespace boost
{
namespace graph
{
namespace detail
{

// true when the visitor was passed with std::ref
template < class Visitor >
struct is_reference_wrapper : std::false_type
{
};

template < class Visitor >
struct is_reference_wrapper< std::reference_wrapper< Visitor > >
: std::true_type
{
};

template < class Visitor >
constexpr bool is_reference_wrapper_v
= is_reference_wrapper< Visitor >::value;

// the visitor type the caller wrote, for concept checks
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 >
struct unwrap_visitor< const std::reference_wrapper< Visitor > >
{
using type = Visitor;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something I like to do is add at C++14 style helper:

template <class Visitor>
using unwrap_visitor_t = typename unwrap_visitor<Visitor>::type

that way at the call sites below instead of having:

using visitor_type
        = typename ::boost::graph::detail::unwrap_visitor< DFSVisitor >::type;

you have instead

using visitor_type = detail::unwrap_visitor_t<DFSVisitor>;

I find this to be more readable.

You can also do C++17 style helpers if you allow template variables (C++14)

template <typename T>
constexpr bool is_reference_wrapper_v = is_reference_wrapper<T>::value

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes on all counts !
I avoided touching the documentation for now, as it's basically a design change and I wanted to keep the PR minimal to simplify review ! 🙏🏽 Thanks a lot Matt !

template < class Visitor >
using unwrap_visitor_t = typename unwrap_visitor< Visitor >::type;

// resolve a std::ref wrapped visitor to the referenced object
template < class Visitor > Visitor& deref_visitor(Visitor& vis)
{
return vis;
}

template < class Visitor >
Visitor& deref_visitor(std::reference_wrapper< Visitor > vis)
{
return vis.get();
}

} // namespace detail
} // namespace graph
} // namespace boost

#endif // BOOST_GRAPH_DETAIL_VISITOR_WRAPPER_HPP
13 changes: 4 additions & 9 deletions include/boost/graph/maximum_adjacency_search.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include <boost/graph/graph_concepts.hpp>
#include <boost/graph/iteration_macros.hpp>
#include <boost/graph/detail/d_ary_heap.hpp>
#include <boost/graph/detail/visitor_wrapper.hpp>
#include <boost/graph/named_function_params.hpp>
#include <boost/graph/visitors.hpp>
#include <boost/property_map/property_map.hpp>
Expand Down Expand Up @@ -123,12 +124,6 @@ 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)
Expand All @@ -151,7 +146,7 @@ void mas_sweep(
auto key_map = pq.keys();

// resolve a std::ref-wrapped visitor to the referenced object
auto& vis_ref = deref_visitor(vis);
auto& vis_ref = ::boost::graph::detail::deref_visitor(vis);

while (!pq.empty())
{
Expand Down Expand Up @@ -218,7 +213,7 @@ 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 >));
using visitor_type = typename mas_detail::unwrap_visitor< MASVisitor >::type;
using visitor_type = ::boost::graph::detail::unwrap_visitor_t< MASVisitor >;
boost::function_requires< MASVisitorConcept< visitor_type, Graph > >();
BOOST_CONCEPT_ASSERT((boost::KeyedUpdatableQueueConcept< KeyedUpdatablePriorityQueue >));

Expand All @@ -231,7 +226,7 @@ void maximum_adjacency_search(
auto key_map = pq.keys();

// resolve a std::ref-wrapped visitor to the referenced object
auto& vis_ref = mas_detail::deref_visitor(vis);
auto& vis_ref = ::boost::graph::detail::deref_visitor(vis);

// seed every vertex with reach count 0
for (const auto& v : make_iterator_range(vertices(g)))
Expand Down
Loading
Loading