tags:

views:

326

answers:

2

Using range based for loops in C++0X, I know we'll be able to do :

std::vector<int> numbers = generateNumbers();

for( int k : numbers )
{
   processNumber( k );
}

(might be even simpler to write with lambda)

But how should i do if I only want to apply processNumber( k ) to a part of numbers? For example, how should I write this for loop for to apply processNumber() to the half (head or tail) of the numbers? Is "slicing" allowed like in Python or Ruby?

A: 

Something like this may work (unchecked as I don't have access to a C++0x compiler),

Edit: Checked it on VS10, of course I had to fix numurous errors....

Define a class which is a proxy to any container and whose iterators only return a subset of the container. The example I supply is the simplest one giving the first half but it can be made much more general.

template <class Container>
class head_t {
    Container& c_;
public:
    template <class T>
    class iter {
        T curr_;
        const T& end_;
        int limit_; // count how many items iterated
    public:
        iter(T curr, const T& end) 
            : curr_(curr)
            , end_(end)             
            , limit_(std::distance(curr_, end_)/2)
            { }

        typename Container::value_type operator*() { return *curr_; }

        // Do the equivilant for for operator++(int)
        iter& operator++() {
            if (--limit_ == 0) // finished our slice
                curr_ = end_; 
            else
                ++curr_;
            return *this;
        }

        bool operator!=(const iter& i) const {
            return curr_ != i.curr_; 
        }
    };

    head_t(Container& c) : c_(c) {}
    iter<typename Container::iterator> begin() { 
        return iter<typename Container::iterator>(c_.begin(), c_.end()); 
    }

    iter<typename Container::iterator> end() {
        return iter<typename Container::iterator>(c_.end(), c_.end()); 
    }    
};

template <class T>
head_t<T> head(T& t) { return head_t<T>(t); }

And then you use it in the loop:

for( int k : head(numbers) )
Motti
Good answer. Note that names ending with `_t` are reserved: http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier
Jon Purdy
+1. Just a couple of suggestions: You should derive the iter class from std::iterator_traits<typename Container::iterator>. But you want this to be an input iterator regardless of the Container, so you should also add "typedef std::input_iterator_tag iterator_category" to the class body. Also, you forgot to implement operator==.
Manuel
That's a lot of work just to achieve what you could do with a `for_each` oneliner.
jalf
@Manuel `iterator` may be a T* in some implementations of `vector` so that wouldn't work...
Motti
@jalf, could you give an example please?
Motti
@Motti - std::iterator_traits is specialized so that it works with pointers
Manuel
@Motti - I think jalf meant something like std::for_each(nums.begin() + 3, nums.end(), func);
Manuel
+1  A: 

One possibility might be boost's iterator_range

(Not having a compiler which supports range-based for, using BOOST_FOREACH instead. I'd expect range-based for work the same, as long as the container or range has the begin and end method.)

#include <boost/foreach.hpp>
#include <boost/range/iterator_range.hpp>
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    BOOST_FOREACH(int n, boost::make_iterator_range(v.begin(), v.begin() + v.size() / 2)) {
        std::cout << n << '\n';
    }
}

For convenience you could also make your own slice function, so it would accept indices instead of iterators. Again, it could be based on boost.iterator_range, or not:

#include <cstddef>
#include <iterator>

template <class Iterator>
class iter_pair
{
public:
    typedef Iterator iterator;
    typedef Iterator const_iterator; //BOOST_FOREACH appears to want this
    iter_pair(iterator first, iterator last): first(first), last(last) {}
    iterator begin() const { return first; }
    iterator end() const { return last; }
private:
    iterator first, last;
};

template <class Container>
struct iterator_type
{
    typedef typename Container::iterator type;
};

template <class Container>
struct iterator_type<const Container>
{
    typedef typename Container::const_iterator type;
};

template <class Container>
iter_pair<typename iterator_type<Container>::type>
    slice(Container& c, size_t i_first, size_t i_last)
{
    typedef typename iterator_type<Container>::type iterator;
    iterator first = c.begin();        
    std::advance(first, i_first);
    iterator last = first;
    std::advance(last, i_last - i_first);
    return iter_pair<iterator>(first, last);
}

template <class Container>
iter_pair<typename iterator_type<Container>::type>
    slice(Container& c, size_t i_last)
{
    return slice(c, 0, i_last);
}

//could probably also be overloaded for arrays

#include <cctype>
#include <string>
#include <boost/foreach.hpp>
#include <iostream>

int main()
{
    std::string s("Hello world, la-la-la!");
    BOOST_FOREACH( char& c, slice(s, 2, 11)) {
        if (c == 'l')
            c = std::toupper(c);
    }
    const std::string& r = s;
    BOOST_FOREACH( char c, slice(r, r.size() - 1) ) {
        std::cout << c << " ";
    }
    std::cout << '\n';
}

Generally one would probably be working with iterators in the first place, so it might not be that useful.

UncleBens
+1 Good solution, definitely the way to go if you're used to working with ranges (as opposed to working with iterators). One optimization I would suggest is to place the line "iterator last = first;" below the line that advances "first". Of course, this means that "last" must be advanced by "i_last-i_first", not "i_last".
Manuel
Yes it's great but what I'm looking for is a for-range loop based way of doing it. I'm feeling that the for-range loop might be "incomplete" without slicing but maybe I just don't know how to do slicing with this new syntaxe?
Klaim
@Klaim: Doesn't it work with the range-for? I can't test and I'm not aware of any new special slicing syntax. As far as I know, anything that provides a begin() and end() method returning iterator-like things goes. @Manuel: Thanks, edited code.
UncleBens