5

I understand that in generic programming, algorithms are decoupled from containers. So it would make no sense to implement a generic algorithm as an instance method (the same algorithm should work on multiple concrete classes; we don't want to make them all inherit from one ABC since it would exponentially increase the number of classes).

But in the case of source() function in the Boost Graph Library, I don't understand why it is a global function and not an instance method of a graph class.

As far as I could tell by reading the BGL source code, source(e, g) needs to know the implementation details of the graph and edge objects passed to it; it's not enough to know just their interfaces.

So source() is not a generic algorithm. In other words, it needs to know the concrete class of the graph instance. Then why not put it in that same class as an instance method? Wouldn't it be a lot cleaner / less confusing than making a global function that needs to be customized to each class it's called upon?

UPDATE

The relevant source code:

  // dwa 09/25/00 - needed to be more explicit so reverse_graph would work.
  template <class Directed, class Vertex,
      class OutEdgeListS,
      class VertexListS,
      class DirectedS,
      class VertexProperty,
      class EdgeProperty,
      class GraphProperty, class EdgeListS>
  inline Vertex
  source(const detail::edge_base<Directed,Vertex>& e,
         const adjacency_list<OutEdgeListS, VertexListS, DirectedS,
                 VertexProperty, EdgeProperty, GraphProperty, EdgeListS>&)
  {
    return e.m_source;
  }


namespace boost {

  namespace  detail {

    template <typename Directed, typename Vertex>
    struct edge_base
    {
      inline edge_base() {} 
      inline edge_base(Vertex s, Vertex d)
        : m_source(s), m_target(d) { }
      Vertex m_source;
      Vertex m_target;
    };
  }
}
max
  • 49,282
  • 56
  • 208
  • 355
  • No reason source(a,b) can't be specialized based upon the types of its parameters. Not everything has to be a member function. Some free functions can be considered part of the interface of a class. Additionally it may be useful to be able to use source() as a shim. Without reading and understanding the code (which is not accessible within 2 clicks of your links) I couldn't really tell you that as I don't use the graph library but they could be things to consider. Alternatively, mail the BGL developers directly and ask about their design decision. I expect there is a good reason for it – Pete Apr 19 '13 at 23:08
  • Is there a reason it bothers you? – Pete Apr 19 '13 at 23:10
  • 2
    http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197 – Benjamin Lindley Apr 19 '13 at 23:21
  • @Pete It does seem strange to me that `edge_base` exposes its `m_source` member. If it's part of its public interface, why even both with `source` function - might as well just let people access `m_source` directly. I will write to the developers, I just thought maybe I'm missing something because I'm not familiar with C++. – max Apr 20 '13 at 00:06

2 Answers2

6

The source(e, g) is not a generic algorithm. Its part of the interface, usually called a concept in C++. The reason for being a non-member function is so that it can be implemented non-intrusively.

Say, for instance, you wanted std::multimap to implement the IncidenceGraph concept. If the graph library required source() to be a member function you would be out of luck, since std::multimap doesn't provide one.

Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
  • I'm sure that's the reason. But why is it bad to inherit from `multimap` and add `source` as a member function? – max Apr 20 '13 at 18:20
  • @max Its not bad, it just doesn't solve the problem of making `std::multimap` an `IncidenceGraph` – Paul Fultz II Apr 20 '13 at 20:22
  • If I understand you correctly, you are talking about the following situation. Suppose I am given an object `data`, which is an instance of `std::multimap`. I want to interpret `data` as a graph by treating key values as graph vertices, and its mapped values as adjacency lists. I can see why the non-member function approach works great here. But what happens if I want to treat `data` as a graph using two different interpretations? I then need to write two sets of non-member functions. How would avoid them colliding with each other? Doesn't BGL require that they are all in the same namespace? – max Apr 21 '13 at 22:35
3

In C++, one should prefer non-member non-friend functions where it is possible to do so. If source can be implemented in terms of the public members of the classes it is designed to operate upon, it should be outside of the class.

This has nothing to do with making things generic algorithms; it is entirely about reducing the amount of code that has access to / can corrupt internal state of the private members of the class.

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • Thanks. It is indeed a non-friend function, because the object it accesses has only public members (in fact, it's a struct). However, I don't think I understand why these members are made public. See [my earlier comment](http://stackoverflow.com/questions/16114616/why-is-boost-graph-librarys-source-a-global-function#comment23015326_16114616). – max Apr 20 '13 at 00:07
  • @max it depends upon whether the class/struct is intended to prevent the violation of an invariant. In this case, perhaps the two vertices that the struct holds have no real invariant that must be maintained (i.e. it is legal if they are the same vertex?). If there is no invariant then why do the members need to be private? Consistency? Alternatively, it might just be that the BGL developers couldn't be bothered to write all the glue code. – Pete Apr 25 '13 at 11:32