looping construct

A while back, Erik Niebler was testing the waters for inclusion of his very, very clever looping construct into boost, but nothing really came of it. Erik's technique is described here: http://www.nwcpp.org/Meetings/2004/01.html Yup, macros are involved and they are yucky, and yup, there already exists a for_each in the standard that works just fine if you care to move your loop body away into a function, and yup, the name BOOST_FOREACH could use a little love. But there is no denying the notational clarity and succinctness of e.g.: foreach(double &d, container) d += 2; Versus the rather redundant: for (list<double>::iterator i = container.begin(); i !=container.end(); ++i) *i += 2; (Note that the latter stops working if container is represented using a vector or a built-in array instead) /Michael Toksvig

michael toksvig wrote:
I was also a bit surprised to find that news of QT4's foreach macro actually made the front page of Slashdot yesterday, and people seemed excited about it. I pulled it because there didn't seem to be enough interest in the Boost community to outweigh the yuk-factor. Should I reconsider? -- Eric Niebler Boost Consulting www.boost-consulting.com

Hi Pavel, what about this Jan Gaspar's circular buffer code. Can it be prebublished somewhere. I would be very grateful. Cheers Lasse -----Ursprüngliche Nachricht----- Von: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] Im Auftrag von Pavel Vozenilek Gesendet: Dienstag, 20. April 2004 23:03 An: boost@lists.boost.org Betreff: [boost] Re: looping construct "Eric Niebler" <eric@boost-consulting.com> wrote:
Do you think it can be made working with BCB 6.4? (it failed to compile last time I checked) /Pavel _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

"Eric Niebler" <eric@boost-consulting.com> writes:
Please do. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

"michael toksvig" <michaeltoksvig@yahoo.com> wrote in message news:c63fk0$ko0$1@sea.gmane.org... | Versus the rather redundant: | for (list<double>::iterator i = container.begin(); i !=container.end(); | ++i) | *i += 2; | | (Note that the latter stops working if container is represented using a | vector or a built-in array instead) not with some collection traits: for( iterator_of< T >::type i = begin( c ); i != end( c ); ++i ) *i +=2; br Thorsten

Thorsten Ottosen wrote:
::type. It also requires you to have an understanding of iterators and half-open sequences, something that is not intuitive to people new to
This is better than a standard for-loop from a maintenance perspective, but not from a usability perspective. It's just as much typing, or more. You still have to specify the container type. And if the container type is dependent, you'll need to insert a "typename" before iterator_of< T the STL way. -- Eric Niebler Boost Consulting www.boost-consulting.com

John Torjo <john.lists@torjo.com> writes:
That said, you do have have to name the container type. At the same time, you don't have to name the element type ;-) -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

Alexander Nasonov wrote:
Another idea. The any_pointer could be made lightweighted. ScopedGuard trick can help, on one hand, to hide ugly container type behind typedef'd reference, and, on another hand, to let the compiler remember the "origin". I suspect the compiler have to be very very clever. // aka ScopedGuard; library code typedef crange_base const& crange_base_ref; // user code for(crange_base_ref r = make_crange(cont); r; ++r) *r += 2; -- Alexander Nasonov Independent Developer and Consultant

Alexander Nasonov <alnsn-mycop@yandex.ru> writes:
I have in fact used such an any_container. Of course, then you're be back to naming the value_type ;-) -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

David Abrahams wrote:
I guess not needing to name the container type will happen only for C++0x. But lot of time 'till then ;) But I'm quite happy with the crange<container> compromise so far, since you do know the container type, once you use it (it's its type, that is :D)
time, you don't have to name the element type ;-)
of course ;) the element type is deduced from the container type. For instance, if you want a constant range, just do: crange<const container> r(cont); //... etc Best, John

John Torjo <john.lists@torjo.com> writes:
But container is usually "spelt something<element_type>", so I'd pick naming the element type over naming the container type any day. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

David Abrahams wrote:
But it's technically not possible. Think about: typedef std::deque<int> d_array; typedef std::vector<int> v_array; d_array d; v_array v; crange<d_array> r(d); // ok crange<v_array> r(v); // ok However, had I used crange<int> r(d); // how do I know what iterators to keep inside? crange<int> r(v); // how do I know what iterators to keep inside? Best, John

John Torjo wrote:
You keep a pointer to crange_impl_base<int> which points to a heap-allocated instance of crange_impl<int, d_array> (or crange_impl<int, v_array> or what have you) which is derived from crange_impl_base<int> and knows what iterators to keep inside. Not very efficient, but not technically impossible either. Another option is to write for (crange<int>& r = mkrange(d); r; ++r) { ... } where mkrange returns crange_impl<int, d_array> which is derived from crange<int>. This doesn't need heap allocation, but still needs virtual operator++ and virtual operator*. A smart compiler could in theory inline them anyway, but I wouldn't count on it. -- Anatoli Tubman PTC Israel (Haifa) Tel. (+972) 4-8550035 ext. 229

"Anatoli Tubman" <anatoli@ptc.com> wrote in message news:4087D552.8000209@ptc.com... | John Torjo wrote: | > | > However, had I used | > crange<int> r(d); // how do I know what iterators to keep inside? | > crange<int> r(v); // how do I know what iterators to keep inside? [snip] | Another option is to write | | for (crange<int>& r = mkrange(d); r; ++r) { ... } | | where mkrange returns crange_impl<int, d_array> which is derived from | crange<int>. | | This doesn't need heap allocation, but still needs virtual operator++ | and virtual operator*. A smart compiler could in theory inline them | anyway, but I wouldn't count on it. virtual dispatching would be most unfortunate here. What about letting crange's constructor take a pointer to crange_impl and forward to crange_impl::operator++() inside crange_impl::operator++() ? br Thorsten

Anatoli Tubman wrote:
Nope. :-)
Not that either. :-) With the FOREACH macro, you don't have to specify the container type, there is no heap allocation and no virtual function calls. Everything is fully-inline-able. And it doesn't need typeof. Anatoli is on the right track. Consider the following (typename left out for clarity): struct base {}; template<class T> struct derived : base { .... mutable T data; }; template<typename T> derived<T::iterator> begin( T &t ) { return derived<T::iterator>( t.begin(); ); } template<typename T> T::reference deref( base const &b, T & ) { return *static_cast< derived<T::iterator> const &>( b ).data; } ... std::vector<int> v( /*...*/ ); base const &b = begin( v ); int &i = deref( b, v ); ... Note the call to deref() above. Since you pass in the original container, its type is deduced and you can use that information to infer the actual type of the object refered to by "b". In this way, you only have to specify the element type, not the container type. -- Eric Niebler Boost Consulting www.boost-consulting.com

Eric Niebler wrote:
Um, I've studied your work with FOREACH and I quite like it, but I thought this particular subthread was about a about a macro-free implementation. It seems that one needs at least one of: preprocessor, container type, virtual dispatch, or auto/typeof :( I don't see how to avoid all four. -- Anatoli Tubman PTC Israel (Haifa) Tel. (+972) 4-8550035 ext. 229

Anatoli Tubman wrote:
I don't see a way to avoid all four, either. (Re-reading this sub-thread.) I just thought we were looking for a better implementation, not necessarily a macro-free one, and John suggested that something wasn't possible. I was responding to that. -- Eric Niebler Boost Consulting www.boost-consulting.com

John Torjo wrote:
and yet, it seems possible ;) see my other post
You're using function pointers, which are not really different from virtual dispatch and incur about the same performance penalty (IMHO). -- Anatoli Tubman PTC Israel (Haifa) Tel. (+972) 4-8550035 ext. 229

Eric Niebler wrote:
Your technique is indeed very cool and useful, when you want to only go forward ;) The problem is if you need "custom incrementing". Like, sometimes you might want to go back, or twice forward and such. Or, when you need two consecutive values, like *r and *(r-1) for ( crange<container> r(cont); r; ++r) { if ( *r == 10) { *(--r) = 0; ++r; } } Best, John

Anatoli Tubman wrote:
Later this approach could be changed either to for (auto r = mkrange(d); r; ++r) { ... } // TODO: correct the idea if auto strip top-level reference off or direct compiler support of foreach construct. -- Alexander Nasonov Independent Developer and Consultant

Anatoli Tubman wrote:
This is in reference to the following line of code: for (crange<int> const & r = mkrange(d); r; ++r) Making the assignment operator private would do nothing since it is not being called here. Making the copy constructor private wouldn't help either because the copy constructor must be accessible in order to bind a temporary to a const reference. (Not all compilers enforce this rule.) -- Eric Niebler Boost Consulting www.boost-consulting.com

John Torjo <john.lists@torjo.com> writes:
It works with Eric's FOR_EACH macro. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

Like the following: I guess it depends on how much you're willing to pay for syntactic sugar... //----------------------------------------------------------------------------- #include <iostream> #include <vector> #include <deque> #include <list> #include <utility> #include "each.hpp" template<typename T1, typename T2> std::ostream& operator<< (std::ostream& os, const std::pair<T1, T2>& p) { return os << "{" << p.first << "," << p.second << "}"; } int main(int, char**) { std::vector<int> int_vector; int_vector.push_back(5); int_vector.push_back(10); for (each<int> in(int_vector); in; ++in) std::cout << *in << std::endl; std::deque<double> double_deque; double_deque.push_back(3.14); double_deque.push_front(99.999); for (each<double> in(double_deque); in; ++in) std::cout << *in << std::endl; std::list<std::pair<unsigned, char> > pair_list; pair_list.push_back(std::make_pair(10, 'a'+13)); pair_list.push_back(std::make_pair(11, 'a'+14)); for (each<std::pair<unsigned, char> > in(pair_list); in; ++in) std::cout << *in << std::endl; std::list<int> int_list(3); for (each<int> in(int_list); in; ++in) *in = 5; for (each<const int> in(int_list); in; ++in) std::cout << *in << std::endl; return 0; } //-- each.hpp ----------------------------------------------------------------- #include <boost/function.hpp> #include <boost/bind.hpp> template<typename T> struct each { typedef T value_type; template<typename ContainerType> each(ContainerType& c) : iter(c.begin(), c.end()) {} operator bool(void) const { return iter.at_end() == false; } void operator++(void) { iter.increment(); } value_type& operator*(void) const { return iter.dereference(); } private: struct iterator_wrapper { struct iterator_storage { // Allocate a memory buffer sized to hold a real iterator, and copy it template<typename BaseIteratorType> iterator_storage(const BaseIteratorType& t) : address(operator new(sizeof t)) { BaseIteratorType* typed_address = reinterpret_cast<BaseIteratorType*>(address); *typed_address = t; } ~iterator_storage() { operator delete(address); } void* address; }; template<typename IteratorType> iterator_wrapper(IteratorType begin, IteratorType end) : current_storage(begin), end_storage(end) { // Make bound references to the operations we need, on the iterator copy IteratorType* saved_begin = reinterpret_cast<IteratorType*>(current_storage.address); increment = boost::bind(&IteratorType::operator++, saved_begin); dereference = boost::bind(&IteratorType::operator*, saved_begin); IteratorType* saved_end = reinterpret_cast<IteratorType*>(end_storage.address); at_end = boost::bind(&iterator_wrapper::is_equal<IteratorType>, saved_begin, saved_end); } template<typename IteratorType> static bool is_equal(const IteratorType* first, const IteratorType* second) { return (*first == *second); } boost::function<void (void)> increment; boost::function<value_type& (void)> dereference; boost::function<bool (void)> at_end; iterator_storage current_storage; iterator_storage end_storage; }; iterator_wrapper iter; };

John Torjo <john.lists@torjo.com> writes:
Calling through internally stored function pointers may be a minor improvement over using virtual functions, but that's not crystal clear to me. It seems likely that compilers might optimize away the virtual calls because all vtables are known to be strictly constant, but the other optimization seems less likely. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

David Abrahams wrote:
Just a late thought: what if we were to make the internal data of crange<> const? I guess this should give an extra hint to the compiler: template< class type> struct crange { protected: typedef crange<type> self_type; typedef void (*incrementer)(const self_type&); typedef bool (*at_end)(const self_type &); typedef type & (*deref)(const self_type&); crange( incrementer inc_func, at_end at_end_func, deref deref_func) : m_inc_func(inc_func), m_at_end_func(at_end_func), m_deref_func(deref_func) {} public: operator bool() const { return !m_at_end_func(*this); } const self_type & operator++() const { m_inc_func(*this); return *this; } type & operator*() const { return m_deref_func(*this); } private: const incrementer m_inc_func; const at_end m_at_end_func; const deref m_deref_func; }; What do you think? Best, John John Torjo Freelancer, builder.com C++ article writer -- mailto:john@torjo.com -- http://www.torjo.com/logview/EasyLogView.exe viewing/filtering logs can't get any easier than this!

John Torjo <john.lists@torjo.com> writes:
Not much; my guess is that compilers very seldom use const object declarations for optimization, but I have no data. At least in the case of vtables the user really has no reasonable way of mutating it by casting away const, but I was only speculating about that, too. Someone should measure. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

John Torjo wrote:
Actually, I was wrong. You can get away, without knowing the container type. Here's a small example (tested on vc7.1): You can actually do: for( crange<int> const & r = mkrange(v); r; ++r) std::cout << "from v " << *r << std::endl; for( crange<int> const & r = mkrange(l); r; ++r) std::cout << "from l " << *r << std::endl; template< class type, class container> struct crange_impl; template< class type> struct crange { protected: typedef crange<type> self_type; typedef void (*incrementer)(const self_type&); typedef bool (*at_end)(const self_type &); typedef type & (*deref)(const self_type&); crange( incrementer inc_func, at_end at_end_func, deref deref_func) : m_inc_func(inc_func), m_at_end_func(at_end_func), m_deref_func(deref_func) {} public: operator bool() const { return !m_at_end_func(*this); } const self_type & operator++() const { m_inc_func(*this); return *this; } type & operator*() const { return m_deref_func(*this); } private: incrementer m_inc_func; at_end m_at_end_func; deref m_deref_func; }; template< class type, class container> struct crange_impl : public crange<type> { typedef crange<type> base; typedef crange_impl<type,container> self_type; crange_impl( container & c) : m_first(c.begin()), m_last(c.end()), base(&self_type::increment, &self_type::at_end, &self_type::deref) {} private: static void increment(const base & me) { const self_type & self = reinterpret_cast<const self_type&>(me); ++self.m_first; } static bool at_end(const base & me) { const self_type & self = reinterpret_cast<const self_type&>(me); return self.m_first == self.m_last; } static type & deref(const base & me) { const self_type & self = reinterpret_cast<const self_type&>(me); return *self.m_first; } private: mutable typename container::iterator m_first, m_last; }; template<class container> crange_impl<typename container::value_type, container> mkrange(container & c) { typedef crange_impl<typename container::value_type, container> impl; return impl(c); } #include <vector> #include <list> #include <algorithm> int _tmain(int argc, _TCHAR* argv[]) { std::vector<int> v; std::list<int> l; v.push_back(1); v.push_back(5); v.push_back(7); v.push_back(9); std::copy( v.begin(), v.end(), std::back_inserter(l)); v.push_back(11); v.push_back(25); v.push_back(37); v.push_back(69); for( crange<int> const & r = mkrange(v); r; ++r) std::cout << "from v " << *r << std::endl; for( crange<int> const & r = mkrange(l); r; ++r) std::cout << "from l " << *r << std::endl; return 0; }

"Daniel Wallin" <dalwan01@student.umu.se> wrote in message news:c65o14$n90$1@sea.gmane.org... | John Torjo wrote: | | > michael toksvig wrote: | >> But there is no denying the notational clarity and succinctness of e.g.: | >> foreach(double &d, container) | >> d += 2; | >> | >> | >> | > You can also use the rtl (Ranges Template Library): | > (note: I'll rename the library, wince there's another one with the same | > name ;)) | > | > for( crange<container> r(cont); r; ++r) | > *r += 2; | | xxx::for_each(cont, _1 += 2); | | IMO C++ isn't missing a loop construct, it's missing a lambda construct. but you cannot call break such a loop. With for each + auto we could say for( auto i : cont ) *i += 2; br Thorsten

Daniel Wallin wrote:
xxx::for_each(cont, _1 += 2);
IMO C++ isn't missing a loop construct, it's missing a lambda construct.
IMO, C++ is missing a foreach construct *and* a lambda construct. :-) When the loop body is non-trivial, writing it as a lambda would make the code hard to follow. I shudder at the thought of a lambda that takes up more than a few lines. A foreach construct would make it easier to read, in that case. -- Eric Niebler Boost Consulting www.boost-consulting.com

Eric Niebler wrote:
Why? I don't see why there should be much of a difference. foreach (x, v) { ... } vs for_each(v, lambda(x) { // or whatever a lambda syntax would look like ... });
But then again, if we had a type inference mechanism and a range library the for-loop is easy enough: for (auto r = range(v); r; ++r) { } I would agree that there would be need for a simpler loop head if we had to write every trivial loop on our own, but we have standard algorithms. That said; there might be need for your macro right now, since we don't have auto or lambda yet. :) -- Daniel Wallin

Daniel Wallin <dalwan01@student.umu.se> writes:
And they increasingly implement optimizations like loop unrolling. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com
participants (11)
-
Alexander Nasonov
-
Anatoli Tubman
-
Daniel Wallin
-
David Abrahams
-
Eric Niebler
-
John Torjo
-
lars@takomat.com
-
Matthew Vogt
-
michael toksvig
-
Pavel Vozenilek
-
Thorsten Ottosen