9

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?

Andreas Bonini
  • 44,018
  • 30
  • 122
  • 156
Klaim
  • 67,274
  • 36
  • 133
  • 188
  • 4
    It would probably just be easier to do `std::for_each(from, to, [] (int k) { processNumber(k); });`. Or you would have to provide a for each compatible sub-range in that vector. – Nick Bedford Feb 03 '10 at 23:04
  • Yes, I'm aware of that. I just want to know the limites of for-range loop in C++ compared to other languages where slicing is "easy". – Klaim Feb 04 '10 at 08:23
  • Isn't the `for_each` solution shown above "easy"? – jalf Feb 07 '10 at 01:02
  • @Nick, this assumes that `numbers` is the numbers `from..to` whereas (as I understand the question) they may be any numbers and you just want to process some of them (e.g. `s/generateNumbers()/firstPrimes(100)/`) – Motti Feb 07 '10 at 08:07

3 Answers3

14

You can use the "sliced" range adaptor from the Boost.Range library:

#include <boost/range/adaptor/sliced.hpp>

using boost::adaptors::sliced;

...

std::vector<int> numbers = generateNumbers();
for( int k : numbers | sliced(0, numbers.size() / 2))
{
     processNumber( k );
}   
HighCommander4
  • 50,428
  • 24
  • 122
  • 194
3

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
  • 40,819
  • 6
  • 57
  • 90
  • +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 Feb 06 '10 at 17:27
  • 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 Feb 06 '10 at 17:40
  • 1
    @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 Feb 06 '10 at 19:29
0

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
  • 110,860
  • 49
  • 189
  • 262
  • 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 Feb 06 '10 at 12:45
  • +1. Just a couple of suggestions: You should derive the iter class from std::iterator_traits. 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 Feb 06 '10 at 17:40
  • That's a lot of work just to achieve what you could do with a `for_each` oneliner. – jalf Feb 07 '10 at 01:03
  • @Manuel `iterator` may be a T* in some implementations of `vector` so that wouldn't work... – Motti Feb 07 '10 at 08:05
  • @jalf, could you give an example please? – Motti Feb 07 '10 at 08:05
  • @Motti - std::iterator_traits is specialized so that it works with pointers – Manuel Feb 07 '10 at 09:12
  • @Motti - I think jalf meant something like std::for_each(nums.begin() + 3, nums.end(), func); – Manuel Feb 07 '10 at 09:14