Boost logo

Boost :

From: Andrei Tovtchigretchko (andrey_at_[hidden])
Date: 2000-01-17 03:01:38


I've been using these things for a while, and they came
handy amazingly often (at least Member_iterator concept).

memb_iterator is an iterator that extracts member of some
class. Example:

struct Box {
    float width;
    float height;
    float depth;
};

vector<Box> vbox;
...
vector<float> vwidth(vbox.size());

copy(memb_iter(vbox.begin(),&Box::width), memb_iter(vbox.end(),&Box::width),
    vwidth.begin()); /* copies 'width' field from each element of 'vbox'
into
    corresponding elements of 'vwidth' */

End of example.

'memb_iter' above is a helper function:
memb_iterator<Iterator> memb_iter(Iterator x, PointerToMember p);
More formal description:
If some class T has member a,
and x is an object of type Iterator whose value_type is T,
and y = memb_iter(x,&T::a)
then
*y++ <==> x++; x->a
Similar example can be written to copy data back from 'vwidth' to 'vbox'.
In terms of database handling, the meaning of that iterator is to
"extract a field from a table" if we consider the container of structures as
a table.
This approach makes it easier to apply generic algorithms to complex data
structures.
My current implementation determines if the original iterator is const or
mutable
and creates corresponding memb_iterator.
The concept must be extended to member functions, so that we could have:
class Box {
private:
    float width;
public:
    float get_width() const { return width; }
    float do_something(float);
};
vector<Box> vbox;
copy(memb_iter(vbox.begin(),&Box::get_width), ... etc.
or:
copy(vwidth.begin(),vwidth.end(),memb_iter(vbox.begin(),&Box::do_something))
;
But that would take some work - I haven't done it.

The next concept is "PipeIterator". The meaning and the usage is generally
the
same as for pipes in Unix shell and such. The purpose is to avoid creating
temporary
containers when we just need to supply the output of one algorithm as the
input to
another. There are ipipe_iterator and opipe_iterator, which stand for input
and output
pipes correspondingly.
Example:
/* suppose we have some random number generator class RandGenerator and
we want to generate 1000 numbers and find their sum */
RandGenerator randgen;
float randsum = accumulate(ipiper(randgen),ipiper(randgen,1000),0);
/* now suppose that we have an object that counts numbers greater than
some threshold - 'UpperCounter' */
struct UpperCounter {
    float level;
    int count;
    UpperCounter(float __level) : level(__level), count(0) {}
    void operator () (float x) { if(x > level) count++; }
};
/* here we count the number of random numbers from the previous
example that are greater than 0.1 */
UpperCounter ucounter(0.1);
copy(ipiper(randgen),ipiper(randgen,1000),opiper(ucounter));

End of example.

The rational behind both MemberIterator and PipeIterator efforts is to
avoid creating temporary storage and ugly adaptor classes. The
"generic" algorithms often expect their input (or make output) as
iterator ranges. These concepts help to provide such iterator
ranges in cheap and non-intrusive way.

What do you think?

Andrei


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk