The best explanation is right on the landing page in the documentation: https://www.boost.org/doc/libs/1_81_0/libs/graph/doc/index.html
It contrasts Genericity in STL [sic] versus Genericity in the Boost Graph Library. The most central blurb from the second section:
[First,] the graph algorithms of the BGL are written to an interface
that abstracts away the details of the particular graph
data-structure. Like the STL, the BGL uses iterators to define the
interface for data-structure traversal. There are three distinct graph
traversal patterns: traversal of all vertices in the graph, through
all of the edges, and along the adjacency structure of the graph (from
a vertex to each of its neighbors). There are separate iterators for
each pattern of traversal.
This generic interface allows template functions such as
breadth_first_search()
to work on a large variety of graph
data-structures, from graphs implemented with pointer-linked nodes to
graphs encoded in arrays. This flexibility is especially important in
the domain of graphs. Graph data-structures are often custom-made for
a particular application. Traditionally, if programmers want to reuse
an algorithm implementation they must convert/copy their graph data
into the graph library's prescribed graph structure. This is the case
with libraries such as LEDA, GTL, Stanford GraphBase; it is especially
true of graph algorithms written in Fortran. This severely limits the
reuse of their graph algorithms.
In contrast, custom-made (or even legacy) graph structures can be used
as-is with the generic graph algorithms of the BGL, using external
adaptation (see Section How to Convert Existing Graphs to the BGL).
External adaptation wraps a new interface around a data-structure
without copying and without placing the data inside adaptor objects.
The BGL interface was carefully designed to make this adaptation easy.
To demonstrate this, we have built interfacing code for using a
variety of graph structures (LEDA graphs, Stanford GraphBase graphs,
and even Fortran-style arrays) in BGL graph algorithms.
But Wait, There's More!
Related, Boost Geometry has a very similar Design Rationale section that goes into a lot more detail on how a generic template library design evolves into the form that BGL also has.
It does so by example, which is very nice, even though the examples are from the domain of computational geometry, of course.
Auxiliary Property Maps
Regarding the colormap (and other auxiliary algorithm state), I'll add:
you can default them either
- using tagged properties (
property<vertex_color_t, default_color_type>
)
- using trait specializations to associate your external maps with your graph type (specializing
graph_property<>
)
having them caller supplied makes it possible to have enormous efficiency gains when re-using the coloring/storage across algorithm invocations
Note most algorithms default the aux property maps. Indeed, DFS also does!
Simplified Sample
Partly based on your previous question code, here's a sample that shows how you can simplify the invocation.
Note that it uses ADL to advantage to avoid boost::
qualifying function names, and uses the named parameters overload of dfs:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/random.hpp>
#include <iomanip>
#include <random>
struct MyVisitor : boost::default_dfs_visitor {
auto discover_vertex(auto u, auto const& g) {
std::cout << "Discover vertex " << u << " (" << g[u] << ")\n";
}
};
template <class V, class E>
requires std::constructible_from<V, std::string> && std::constructible_from<E, double>
auto foo(...) {
using G = boost::adjacency_list<boost::setS, boost::vecS, boost::directedS, V, E>;
G g;
std::mt19937 prng{std::random_device{}()};
generate_random_graph(g, 10, 30, prng);
for (auto e : make_iterator_range(edges(g)))
g[e] = {std::uniform_real_distribution(1.0, 2.0)(prng)};
for (int i = 0; i < 10; ++i)
g[i] = {std::array{"zero", "one", "two", "three", "four", "five", "six", "seven",
"eight", "nine"}
.at(i)};
return std::tuple(std::move(g), 3);
}
struct V { std::string name; };
struct E { double length; };
static inline auto& operator<<(std::ostream& os, V const& v) { return os << quoted(v.name); }
int main() {
auto [g, root] = foo<V, E>();
depth_first_search(g, visitor(MyVisitor{}));
print_graph(g);
}
Prints, e.g.
Discover vertex 0 ("zero")
Discover vertex 5 ("five")
Discover vertex 1 ("one")
Discover vertex 2 ("two")
Discover vertex 4 ("four")
Discover vertex 6 ("six")
Discover vertex 8 ("eight")
Discover vertex 3 ("three")
Discover vertex 7 ("seven")
Discover vertex 9 ("nine")
0 --> 5 6 7
1 --> 0 2 7
2 --> 1 4 5 6 8
3 --> 0 4 8
4 --> 1 2 6
5 --> 0 1 2 7
6 --> 2 5 8
7 --> 2 5
8 --> 0 3
9 --> 3 7