Home > Software engineering >  Do Boost Graph bundled properties need to be default constructible?
Do Boost Graph bundled properties need to be default constructible?

Time:12-23

My objective (background)

I want to use the Boost Graph adjacency_list with bundled properties, but separating the user-defined properties class from the actual (and noisy) instantiation of the graph.

In the function that consumes the bundles to instantiate the adjacency_list, I ended up adding these constraints to the signature:

template<class VertexProperties, class EdgeProperties>
      requires std::constructible_from<VertexProperties, std::string> &&
               std::constructible_from<EdgeProperties, double>
auto foo(...){ /* instantiate the adjacency list */}

But I was surprised that the constraints did not seem sufficient, as it seems the bundled properties also have to be default constructible? I don't understand why.

What I tried

My problem boils down to the following code, that also lives on Compiler Explorer. Desactivating the faulty constructor and activating the default constructor will make it compile and run.

#include <boost/graph/adjacency_list.hpp>
#include <iostream>
#include <numeric>

struct my_vertex{
  std::string name;
  //my_vertex(std::string s="default"): name{s} { std::cout << "vertex built!" << std::endl; }
  my_vertex(std::string s): name{s} { std::cout << "vertex built!" << std::endl; }
};

struct my_edge{
  double length;
  //my_edge(double d=1.0): length{d} {std::cout << "edge built!" << std::endl;}
  my_edge(double d): length{d} {std::cout << "edge built!" << std::endl;}
};

int main() {
    using graph_t = boost::adjacency_list<
      boost::setS,
      boost::vecS,
      boost::directedS,
      my_vertex,
      my_edge>;

    graph_t graph;
    auto u = add_vertex(my_vertex{"toto"}, graph);
    auto v = add_vertex(my_vertex{"toto"}, graph);
    add_edge(u, v, my_edge{1.0}, graph);
}

My question

Obviously: "what am I doing wrong?" But it's also wrapped up with a bunch of others interrogations I have with bundled properties:

  1. In the documentation, POD are used, I did not see any explicit constructors. Why?
  2. Why do we have to pass an explicit my_vertex{"toto"}and not simply a std::string: I would have thought the function could forward the required parameters to the constructor of the bundled property?
  3. Why does it need to be default constructible (and why the compiler detects that way too late in the internals of BGL) ?

CodePudding user response:

Do property bundles need to be default constructible?

Yes.

  1. In the documentation, POD are used, I did not see any explicit constructors. Why?

They're not expressly POD. Types that can be aggregate-initialized aren't typically POD (that implies triviality and standard-layout).

  1. Why do we have to pass an explicit my_vertex{"toto"}and not simply a std::string: I would have thought the function could forward the required parameters to the constructor of the bundled property?

In generic contexts argument types aren't always deduced. In this case you're passing char[5] and the constructor requires another implicit conversion. A two-step implicit conversion (char[5] -> std::string -> my_vertex const& is not considered by compilers).

You can of course have your cake and eat it in several ways:

auto    u = add_vertex({"toto"}, graph);
auto    v = add_vertex({"kansas"}, graph);

Or, if you must:

template <typename... I> my_vertex(I&&... init) : name(std::forward<I>(init)...) {
    std::cout << "vertex built!" << std::endl;
}

Now both work:

auto    u = add_vertex({"toto"}, graph);
auto    v = add_vertex("kansas", graph);
  1. Why does it need to be default constructible (and why the compiler detects that way too late in the internals of BGL) ?

Because that's how the library is specified. If they aren't, you would have to provide factories for any algorithm that may construct vertices/edges.

In that sense, it is certainly not "way too late" for the compiler to notice: partial template instantiation means that you only need to provide what is actually used. That's a feature.


I'd invert the question. Why would you add the constructor? Is it just to be able to drop the {} around the initializers? In cases where that would make sense to me, why don't you just use the member type as the bundle?

#include <boost/graph/adjacency_list.hpp>

using my_vertex = std::string;
using my_edge   = double;

int main() {
    using graph_t = boost::adjacency_list<boost::setS, boost::vecS, boost::directedS,
                                          my_vertex, my_edge>;

    graph_t graph;
    auto    u = add_vertex("toto", graph);
    auto    v = add_vertex("kansas", graph);
    add_edge(u, v, 1.0, graph);
}

Or indeed, just use aggregate initialization:

struct my_vertex { std::string name; };
struct my_edge   { double length;    };

int main() {
    using graph_t = boost::adjacency_list<boost::setS, boost::vecS, boost::directedS,
                                          my_vertex, my_edge>;

    graph_t graph;
    auto    u = add_vertex({"toto"}, graph);
    auto    v = add_vertex({"kansas"}, graph);
    add_edge(u, v, {1.0}, graph);
}

Note that the constraints for aggregate types have been loosened over the years, and you can have aggregate initialization in types with custom constructors by also = default-ing the default constructor.

  • Related