This answer shows a std::unique_ptr
as a member of a bundled property with boost-graph.
The provided example can be tried out Live On Coliru.
As I am only interested in the design aspect and not the algorithmic part of that answer, I provide a simplified example Live On Coliru.
#include <iostream>
#include <fstream>
#include <boost/graph/adjacency_list.hpp>
struct custom_node{
custom_node(std::string name, int capacity) : name(name), capacity(capacity) {}
std::string name = "uninitialized";
int capacity = -1;
};
void usage(){}
using namespace boost;
struct VertexProperties {
int id;
std::unique_ptr<custom_node> node;
};
typedef adjacency_list<vecS, vecS, directedS, VertexProperties> DirectedGraph;
typedef graph_traits<DirectedGraph>::vertex_descriptor custom_vertex;
typedef graph_traits<DirectedGraph>::edge_descriptor custom_edge;
int main() {
DirectedGraph g;
boost::add_vertex(g);
g[0].node = std::make_unique<custom_node>("inner", 2);
////boost::add_vertex(VertexProperties{0, std::make_unique<custom_node>("inner", 2)}, g); // compilation error
std::cout << boost::num_vertices(g);
std::cout << g[0].id << "\n";
std::cout << g[0].node->name << "\n";
std::cout << g[0].node->capacity << "\n";
}
The vertex can be added via boost::add_vertex(g)
and then the std::unique_ptr
can be instantiated.
If I try to to achieve the same thing by creating the vertex with all properties via list initialization in add_vertex
, there is a compilation error about the implicitly-deleted copy constructor of 'VertexProperties'
.
VertexProperties
, of course, has its copy constructor deleted, because std::unique_ptr
has its copy constructor deleted.
Why is the copy constructor even necessary for list initialization in the first place? Is there something I do not understand about it, or is this a shortcoming of boost-graph?
Why is the copy constructor is necessary for list initialization in the first place? Is there something I do not understand about it, or is this a shortcoming of boost-graph?
That's not a thing and also not what's happening.
Aside: in copy-initialization the assignment can be elided by the compiler, but operator= still needs to be accessible for it to be valid code.
But in this case it's just about the library code not being move-aware. You have to realize that "bundled" properties are separately stored. The bundle (as any property) is required to be default constructible anyways (so add_vertex(g)
works) so the implementation is simplified by always assigning to the default-constructed property.
Since it's not move-aware, assignment will not forward the rvalue and things don't compile.
The linked answer already showd:
if(g[v].node_logic) {
g[v].node.reset(new custom_node(g[v].vertex_name, 0, standby, normal));
}
More options:
VertexProperties props {0,
std::make_unique<custom_node>("inner", 2)};
auto vd = boost::add_vertex(g);
g[vd] = std::move(props);
Wrap yo' shit! You can create any interface you prefer:
auto add_vertex = [&g](VertexProperties&& props) {
auto vd = boost::add_vertex(g);
g[vd] = std::move(props);
return vd;
};
add_vertex({0, std::make_unique<custom_node>("inner", 2)});
You can even elaborate on that:
auto add_vertex = [&g](int id, std::string name, int capacity = -1) {
auto vd = boost::add_vertex(g);
g[vd] = { id, std::make_unique<custom_node>(name, capacity) };
return vd;
};
add_vertex(0, "inner", 2);
add_vertex(1, "outer", 3);
add_vertex(2, "other");
All the above options Live On Coliru
The latter interface is far superior anyways, if you ask me.
If you want you can use ADL to make it available to others:
#include <boost/graph/adjacency_list.hpp>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <memory>
namespace MyLib { // for ADL demo
struct custom_node {
custom_node(std::string name, int capacity)
: name(std::move(name)), capacity(capacity) {}
std::string name = "uninitialized";
int capacity = -1;
friend std::ostream& operator<<(std::ostream& os, custom_node const& cn) {
return os << "{" << std::quoted(cn.name) << ", " << cn.capacity << "}";
}
};
struct VertexProperties {
int id{};
std::unique_ptr<custom_node> node;
friend std::ostream& operator<<(std::ostream& os, VertexProperties const& vp) {
os << vp.id;
if (vp.node)
os << ", " << *vp.node;
return os;
}
};
template <typename G>
auto add_vertex(G& g, int id, const std::string& name, int capacity = -1) {
auto vd = boost::add_vertex(g);
g[vd] = { id, std::make_unique<custom_node>(name, capacity) };
return vd;
}
}
using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, MyLib::VertexProperties>;
using custom_vertex = Graph::vertex_descriptor;
using custom_edge = Graph::edge_descriptor;
int main() {
Graph g;
add_vertex(g, 10, "inner", 2);
add_vertex(g, 11, "outer", 3);
add_vertex(g, 12, "other");
for (auto vd : boost::make_iterator_range(vertices(g))) {
std::cout << g[vd] << "\n";
}
}
Prints
10, {"inner", 2}
11, {"outer", 3}
12, {"other", -1}
If all you want is unique ownership with optional/lazy construction, why not:
struct VertexProperties {
int id{};
std::optional<custom_node> node;
};
Or even just
struct VertexProperties {
int id{};
custom_node node;
};
The ownership semantics will be the same, without the costs:
Graph g;
add_vertex({10, custom_node{"inner", 2}}, g);
add_vertex({11, custom_node{"outer", 3}}, g);
add_vertex({12, custom_node{"other"}}, g);
That's just using the standard boost::add_vertex
overloads from BGL. Without optional<>
it can become even simpler:
add_vertex({10, {"inner", 2}}, g);
add_vertex({11, {"outer", 3}}, g);
add_vertex({12, {"other"}}, g);
Also Live On Coliru (without std::optional: Live)
#include <boost/graph/adjacency_list.hpp>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <memory>
#include <optional>
struct custom_node {
custom_node(std::string name, int capacity = -1)
: name(std::move(name)), capacity(capacity) {}
std::string name = "uninitialized";
int capacity = -1;
friend std::ostream& operator<<(std::ostream& os, custom_node const& cn) {
return os << "{" << std::quoted(cn.name) << ", " << cn.capacity << "}";
}
};
struct VertexProperties {
int id{};
std::optional<custom_node> node;
friend std::ostream& operator<<(std::ostream& os, VertexProperties const& vp) {
os << vp.id;
if (vp.node) os << ", " << *vp.node;
return os;
}
};
using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, VertexProperties>;
using custom_vertex = Graph::vertex_descriptor;
using custom_edge = Graph::edge_descriptor;
int main() {
Graph g;
add_vertex({10, custom_node{"inner", 2}}, g);
add_vertex({11, custom_node{"outer", 3}}, g);
add_vertex({12, custom_node{"other"}}, g);
for (auto vd : boost::make_iterator_range(vertices(g))) {
std::cout << g[vd] << "\n";
}
}
Prints
10, {"inner", 2}
11, {"outer", 3}
12, {"other", -1}