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.
OPTIONS
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:
Live On Coliru
#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}
SOMEONE MENTIONED DESIGN?
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}