Draess
Draess

Reputation: 218

iterators to range of elements in a vector whose attributes have specific value

I have a vector of objects and I want to return the range of elements whose attribute have a specific value. This is the structure:

class A {
public:
  std::vector<B*> vec_;
  pair<vector<B*>::iterator, vector<B*>::iterator> getElements(unsigned int attr_val);
  unsigned int name() { return name_; }
private:
  unsigned int name_;
};

class B {
public:
  unsigned int attr() { return attr_; }
  A* source() { return source_; }
  B* dest() { return dest_; }
private:
  A* source_;
  B* dest_;
  unsigned int attr_;
};

The vector vec_ is already sorted by attr_ and dest_->name() (in that order). Now I want to return all elements, whose attr_ is equal to attr_val.

What is the appropriate stl algorithm (or is there even a vector member function?) to implement getElements(unsigned int attr_val)?

Upvotes: 1

Views: 532

Answers (2)

Steve Jessop
Steve Jessop

Reputation: 279255

You need a value to pass to equal_range, and the obvious thing to use is a pointer. The obvious way to get one is just to create an instance of B with the correct value of attr_, and write a comparator that involves only the value of attr(). I'm going to assume you already know how to do that since you managed to get the vector sorted ;-)

Provided there are no null pointers in the vector you could do this instead:

struct FindAttr {
    unsigned int attr;
    FindAttr(unsigned int attr) : attr(attr) {}
    bool operator()(B *left, B *right) {
        unsigned int leftval = left ? left->attr() : attr;
        unsigned int rightval = right ? right->attr() : attr;
        return leftval < rightval;
    }
};

...

return equal_range(vec_.begin(), vec_.end(), nullptr, FindAttr(value));

You could make that a lambda:

return equal_range(vec_.begin(), vec_.end(), nullptr, [=value](B *left, B *right) {
    unsigned int leftval = left ? left->attr() : attr;
    unsigned int rightval = right ? right->attr() : attr;
    return leftval < rightval;
});

You can actually cut out the pointer altogether, to give what I think is the "cleanest" solution:

struct FindAttr {
    bool operator()(B *left, unsigned int rightval) {
        return left->attr() < rightval;
    }
    bool operator()(unsigned int leftval, B *right) {
        return leftval < right->attr();
    }
};

...

return equal_range(vec_.begin(), vec_.end(), value, FindAttr());

AFAIK there's no direct equivalent to this with a lambda, since lambdas can only have one call signature. I suppose you could write a lambda that takes a boost::variant (or any type that implicitly converts from both unsigned int and from B*, and that remembers which one it was).

Upvotes: 2

Gorpik
Gorpik

Reputation: 11028

You are looking for std::equal_range, which basically does what you need. The interface is:

pair<It, It> equal_range(It first,
                         It last,
                         const T& value,
                         Compare comp);

comp defaults to std::less, which in turn calls operator <.

In your case, an implementation could be:

bool comparer(B* el, unsigned int value)
{
  return el->attr() < value;
}

pair<vector<B*>::iterator, vector<B*>::iterator> A::getElements(unsigned int attr_val)
{
  return equal_range(vec_.begin(), vec_end(), attr_val, comparer);
}

Upvotes: 0

Related Questions