I warned you the other day:
- I choose
listS
because it offers reference/descriptor stability. This however implies there is no implicit vertex_index_t
property.
- If you do make it
vecS
, then you'll have conflict with the id type (size_t) in overload resolution
- In the PS. I remebered that that vertex_index is assumed (by BGL algorithms) to map to the contiguous domain [0, num_vertices(g)) so the given values would not satisfy the requirements, making it unsafe to actually use it as the vertex id.. This handsomely explains why if you force it like in your exampe (with
get(&Vertex::id, g)
) it will not go well.
Checking Some Things
Under 3., let's check the Documentation for breadth_first_search
and yes, it explicitly states:
IN: vertex_index_map(VertexIndexMap i_map)
This maps each vertex to an integer in the range [0, num_vertices(g)).
So, don't do what you tried! It's going to lead to Undefined Behaviour. But read on:
This parameter is only necessary when the default color property map is used. The type VertexIndexMap must be a model of Readable Property Map. The value type of the map must be an integer type. The vertex descriptor type of the graph needs to be usable as the key type of the map.
Default: get(vertex_index, g)
. Note: if you use this default, make sure your graph has an internal vertex_index property. For example, adjacency_list with VertexList=listS does not have an internal vertex_index property.
The sad news: all your issues were exactly called out.
The good news: we have 3 options to fix it:
- pass your own color map
- pass an external vertex index
- make adjacency_list use
vecS
again, but avoid the overload conflicts
1. Pass Your Own Color Map
You can look at the documentation to see the requirements. The simplest way to satisfy that:
std::map<V, boost::default_color_type> colors;
auto color_map = boost::make_assoc_property_map(colors);
Now you call it:
breadth_first_search( //
g, v1, //
boost::visitor(bfs_visitor_instance) //
.vertex_color_map(color_map) //
);
2. Pass Your Own Vertex Index
Instead you could supply an index. Very similarly:
std::map<V, int> index;
auto index_map = boost::make_assoc_property_map(index);
But this time you have to make sure the map is populated according to expectations!
// important: remember to make the data up-to-date!
for (auto v : boost::make_iterator_range(vertices(g)))
index_map[v] = index.size();
And we call it again like:
breadth_first_search( //
g, v1, //
boost::visitor(bfs_visitor_instance) //
.vertex_index_map(index_map) //
);
2b. Intermezzo
Of course you could provide both, although that's not required. It might be an optimization if you call BFS a lot of times or want to use your own data structure (like a flat_map<V, color, small_vector<V, N> >
so you can avoid all allocations there):
breadth_first_search( //
g, v1, //
boost::visitor(bfs_visitor_instance) //
.vertex_color_map(color_map) //
.vertex_index_map(index_map) //
);
3. Use VecS...
This has implications for descriptor stability, but let's at least show. I'd suggest using a wrapper type for the id:
struct Id {
size_t _val;
Id(size_t v = {}) : _val(v) {}
// relay some common operations
friend std::ostream& operator<<(std::ostream& os, Id const& id) { return os << "Id:" << id._val; }
friend size_t hash_value(Id const& id) { return boost::hash_value(id._val); }
auto operator<=>(Id const&) const = default;
};
We need hash/equality for the name lookups and operator<<
for printing the graph. Of course, the implementation is trivial.
With that in place, everything "just works" because Graph
has an internal implicit vertex_index
:
boost::breadth_first_search(g, v1, boost::visitor(bfs_visitor_instance));
Live Listing Of All The Above
Live On Coliru
Compiled twice, with or without USE_VCES
defined:
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/breadth_first_search.hpp>
#include <boost/graph/graph_utility.hpp>
#include <iostream>
#ifndef USE_VECS
using VertexS = boost::listS;
using Id = size_t;
#else
using VertexS = boost::vecS;
struct Id {
size_t _val;
Id(size_t v = {}) : _val(v) {}
// relay some common operations
friend std::ostream& operator<<(std::ostream& os, Id const& id) { return os << "Id:" << id._val; }
friend size_t hash_value(Id const& id) { return boost::hash_value(id._val); }
auto operator<=>(Id const&) const = default;
};
#endif
struct Vertex {
Id id;
std::string other = "default-constructed";
};
using Graph =
boost::adjacency_list<boost::vecS, VertexS, boost::directedS, Vertex>;
// traits
template <> struct boost::graph::internal_vertex_name<Vertex> {
struct type {
using result_type = decltype(Vertex::id);
result_type const& operator()(Vertex const& bundle) const {
return bundle.id;
}
};
};
template <> struct boost::graph::internal_vertex_constructor<Vertex> {
struct type {
private:
using extractor = typename internal_vertex_name<Vertex>::type;
using name_t = std::decay_t<typename extractor::result_type>;
public:
using argument_type = name_t;
using result_type = Vertex;
result_type operator()(const name_t& id) const { return {id}; }
};
};
using V = Graph::vertex_descriptor;
struct simple_bfs_visitor : boost::default_bfs_visitor {
void discover_vertex(V, const Graph&) const {
std::cout << "hi from bfs" << '\n';
}
};
int main() {
Graph g;
V v1 = add_vertex(1111, g);
/*V v2 =*/ add_vertex(2222, g);
/*V v3 =*/ add_vertex(3333, g);
add_edge(Id{1111}, Id{2222}, g);
add_edge(Id{2222}, Id{3333}, g);
print_graph(g, get(&Vertex::id, g), std::cout << "---\n");
print_graph(g, get(&Vertex::other, g), std::cout << "---\n");
simple_bfs_visitor bfs_visitor_instance{};
// 1. bring your own colors
std::map<V, boost::default_color_type> colors;
auto color_map = boost::make_assoc_property_map(colors);
breadth_first_search( //
g, v1, //
boost::visitor(bfs_visitor_instance) //
.vertex_color_map(color_map) //
);
// 2. bring your own index
std::map<V, int> index;
auto index_map = boost::make_assoc_property_map(index);
// important: remember to make the data up-to-date!
for (auto v : boost::make_iterator_range(vertices(g)))
index_map[v] = index.size();
breadth_first_search( //
g, v1, //
boost::visitor(bfs_visitor_instance) //
.vertex_index_map(index_map) //
);
// 2b. bring your own
breadth_first_search( //
g, v1, //
boost::visitor(bfs_visitor_instance) //
.vertex_color_map(color_map) //
.vertex_index_map(index_map) //
);
#ifdef USE_VECS
// 3. use vecS but not `size_t` as the Id:
boost::breadth_first_search(g, v1, boost::visitor(bfs_visitor_instance));
#endif
}
Compile and run:
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -DUSE_VECS -o vecS.exe
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -o listS.exe
set -x
./vecS.exe
./listS.exe
Prints
./vecS.exe
./listS.exe
+ ./vecS.exe
---
Id:1111 --> Id:2222
Id:2222 --> Id:3333
Id:3333 -->
---
default-constructed --> default-constructed
default-constructed --> default-constructed
default-constructed -->
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs
+ ./listS.exe
---
1111 --> 2222
2222 --> 3333
3333 -->
---
default-constructed --> default-constructed
default-constructed --> default-constructed
default-constructed -->
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs
hi from bfs