Martin
Martin

Reputation: 4862

How to generalize an iterator to a certain type

Consider these two functions, which work well for a std::vector:

  int connectNode(GraphNode const& newNode,std::vector<GraphNode const*>::const_iterator beginCandidates, std::vector<GraphNode const*>::const_iterator endCandidates){
    int connections =0;
    for (auto iter= beginCandidates; iter!=  endCandidates; ++iter) {
      if(connectNodes(newNode,**iter)) ++connections;
    }
    return connections;
  }

  int connectNode(GraphNode const& newNode,std::vector<GraphNode>::const_iterator beginCandidates, std::vector<GraphNode>::const_iterator endCandidates){
    int connections =0;
    for (auto iter= beginCandidates; iter!=  endCandidates; ++iter) {
      if(connectNodes(newNode,*iter)) ++connections;
    }
    return connections;
  }

These functions work well for vectors but evidently not for any otehr container, e.g. a set. How could they be generalised. The only possible solution I can think of is with some quite ugly enable_if workaround. Is there a straight forward solution ? Edit: To make it clearer: I want both functions, one for normal containers, one for pointer containers. The real logic happens inside connetNodes, which takes two references. (mind the ** in the first function)

Upvotes: 2

Views: 141

Answers (2)

Praetorian
Praetorian

Reputation: 109089

Take the iterator as a template parameter, then you don't have to worry what kind of container you're iterating over.

template<class T, class Iter>
int connectNode( GraphNode const& newNode, 
                 Iter beginCandidates, 
                 Iter endCandidates )
{
  // ...
}

I don't see the other template parameter (T) being used anywhere in the function template, but I'm assuming your real code uses it somewhere.

Also, you can static_assert that the iterator points to a GraphNode const* using std::iterator_traits<Iter>::value_type

static_assert( std::is_same< typename std::iterator_traits<Iter>::value_type
                             GraphNode const *>::value,
               "Iterator must point to 'GraphNode const *'" );

EDIT:

In order to be able to accept iterators that point to either GraphNode or GraphNode const * leave the function template signature the same, but create 2 overloads for a helper that calls connectNodes

bool do_connectNodes( GraphNode const& newNode, GraphNode const *p )
{
  return connectNodes( newNode, *p );
}

bool do_connectNodes( GraphNode const& newNode, GraphNode& n )
{
  return connectNodes( newNode, n );
}

Now, within connectNode change the if condition to

if( do_connectNodes(newNode,*iter) ) ++connections;

and the correct overload will be selected depending on what the iterator is pointing to.

Upvotes: 2

Xeo
Xeo

Reputation: 131789

As was said, make the iterator type a template parameter - this solves the issue of generalizing the iterator itself. For differing between normal GraphNode values, and pointers thereof, you can just use overloading:

template<class T>
T& maybe_deref(T& v){ return v; }

template<class T>
T& maybe_deref(T* p){ return *p; }

And just call that in connectNodes(newNode, maybe_deref(*iter)).

Upvotes: 2

Related Questions