|
Boost : |
From: Yitzhak Sapir (yitzhaks_at_[hidden])
Date: 2002-10-07 15:27:11
On Mon, 7 Oct 2002, Thorsten Ottosen wrote:
> ----- Original Message -----
> From: "Yitzhak Sapir" <yitzhaks_at_[hidden]>
>
> >
> > Why not make an iterator adaptor (initialization_iterator?) that holds a
> list of values with which to initialize. And then pass it to any of the STL
> containers. (I can post my implementation if it's interesting):
> >
> > std::list<int> primes(
> > make_init_iterator<int>(2)(3)(5)(7)(11)(),
> > make_init_iterator<int>());
>
> I would like to see the code.
Included as an attachment.
> Anyway, what would the advanteges of this
> approach?
It seems to me to be more extensible and to integrate better with the
other Boost classes, in my opinion.
> Wouldn't it be slower because the the iterator adapter needs to
> store the values which
> are then copied? (I must admit, that speed was not really an initial
> concern, I focused on easy use). Another issue is the syntax, with the stuff
> above, I don't
> thnk any newbie will use it very often.
Yes, it would be, but I think using a define to eliminate the heap
allocation and vector copy, you'd be left with just building up the vector
and a container copy. This leaves me with two traversals upon the
initializer list, as compared to your one traversal. But initialization
from a constant list is usually done once at the beginning of the
program/module/etc, and I doubt a factor of two at that point in the
program would be that detrimental. I really can't see how a good design
would put this sort of initialization inside an loop.
A more detailed answer:
Your interface is much more readable when used in practice. In the case
of maps, it has a pitfall that one could easily forget to properly place a
value in the list of initializer values (what about map<int, int>) and
then he'd be wondering where he messed up, because your interface can't
show him the place. I dislike any name called "set_cont" because "set"
has so many meanings, besides being an STL class. I'd like initializer()
better. It may be longer but it conveys what is being done.
Also, I think you could make the interface easier by overloading your
adaptor function:
template <class K, class D, class C, class A>
Map_comma_initializer set_cont(std::map<K,D,C,A>& m);
(with similar uses for multimap/set/multiset) as opposed to the way you
have it now. This way you can have the same adaptor function for both
(and this is something that should be done for ease of use).
And lastly, and most importantly, your interface seems to me to be not
extensible to user data types and even this seems like it wouldn't work:
typedef string street;
typedef int house_number;
typedef pair<street, house_number> address;
typedef string surname;
map<address, surname> addresses;
set_map(addresses) = "Washington St.", 30, "Johnson";
With my interface, the beginning user has to learn a bit, but he doesn't
have the pitfall of the map, and he can easily figure out how to
initialize an arbitrary data type (map<int, shared_ptr<Fruit> >) once he
understands the basic concepts.
I also think my interface integrates better with the Boost methodology.
For example, I think the same effect of your simpler enum_n_from can be
achieved using make_counting_iterator(). (The step-size portion cannot,
unfortunately, and I'd have liked counting iterator to have step size for
integer data types).
As far as operation complexity: Mine does build it up elsewhere but
this is required if the library is to be used for (true) initialization.
(Unless you can use compile-time constructor of the initializer). I build
it in a vector and I copy the vector when I generate the iterator (in the
() call), into a heap-allocated vector, to be safe. So all in all, I do a
heap allocation and vector copy, as well as build up the vector. A smart
STL implementation would probably make the vector copy faster than
building the vector because it already knows what it needs to copy and how
much space to allocate. But even so, this is a lot more than your
implementation. In most cases, though, the copies will probably be of
small objects, and for 99% of the cases the heap allocation and vector
copy can probably be left out (using either a compile-time policy or an
ifdef). And all this means there would be one traversal to build up the
initialization vector, and another to copy the initialized values to a
container.
std::list<int> l(
make_init_iterator<int>(2)(3)(5)(7)(11)(),
make_init_iterator<int>());
typedef std::map<int, std::string> map_t;
map_t m(
make_init_iterator<map_t::value_type>
(map_t::value_type(2, "hi"))
(map_t::value_type(3, "hello"))
(),
make_init_iterator<map_t::value_type>()
);
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk