14

Is it possible to get a const iterator from a vector that can only iterate a certain range of the vector before being invalidated?

For example if I have a vector of 10 elements, I want to return an iterator of elements 4 to 7.

pseudo-code:

int main()
{
    std::vector<int> vector = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    auto iterator = GetRangedIterator(vector, 4, 7)
    for (const int& num : iterator)
        print num;      // 4, 5, 6, 7
}
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
KaiserJohaan
  • 9,028
  • 20
  • 112
  • 199
  • 2
    Iterators don't become "invalidated", they just become equal to the end iterator. Since you're free to use whatever iterator you want as the end, this is trivial. – Mark Ransom May 29 '15 at 23:04

5 Answers5

19

This is pretty trivial to do (though I'd call the result a range, not an iterator).

A simple implementation would look something like this:

template <class Iter>
class range {
    Iter b;
    Iter e;
public:

    range(Iter b, Iter e) : b(b), e(e) {}

    Iter begin() { return b; }
    Iter end() { return e; }
};

template <class Container>
range<typename Container::iterator> 
make_range(Container& c, size_t b, size_t e) {
    return range<typename Container::iterator> (c.begin()+b, c.begin()+e);
}

As it stands right now, this follows normal C++ conventions (0-based counting, the end you specify is past the end of the range, not in it) so to get the output you asked for, you'd specify a range of 3, 7, like:

for (int num : make_range(vector, 3, 7))
    std::cout << num << ", ";      // 4, 5, 6, 7,

Note that the range-based for loop knows how to use begin and end member functions to tell it the range it's going to iterate, so we don't have to deal with invalidating iterators or anything like that, we just have to specify the beginning and end of the range we care about.

T.C.
  • 133,968
  • 17
  • 288
  • 421
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • The only downside I see to this is when `begin()+x` is not an efficient operation for the `Container`, such as for a `std::list`. So you spend a lot of time just determining the bounds of the range before you can then use it. A `std::vector` would not suffer from that, for instance. – Remy Lebeau May 29 '15 at 23:08
  • 3
    @RemyLebeau: Luckily, `begin()+x` wouldn't compile for `std::list`, or any other container which doesn't have random access iterators. – Benjamin Lindley May 29 '15 at 23:11
  • excellent answer. I'd add `const` overloads for `begin` and `end`. – The Paramagnetic Croissant May 30 '15 at 05:29
  • I like this approach, very nice. Always used boost before, but nice to have a way using pure STL. – Baldrick May 30 '15 at 08:35
6

You could use the range-v3 library that is the bases of a Ranges TS which will been part of C++20, but the library already works with C++11 compilers. Here's how:

#include <range/v3/all.hpp>
#include <iostream>
#include <vector>

int main() 
{
    using namespace ranges;

    auto v = view::iota(1, 11) | to_<std::vector<int>>();
    std::cout << view::all(v)  << '\n';

    auto rng = v | view::slice(3, 7); 
    std::cout << rng << '\n';
}

Live Example.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • I Suggest you amend your example to start with OP's vector explicity; the way your code is now confuses, so I believe, newbies who think the use of `iota` or `to` might be part of the solution to getting the iterator rather than setup for a test. – einpoklum Jan 07 '18 at 13:07
  • whats the data type of auto – JFFIGK Nov 19 '18 at 18:52
4

You can use this

for( it = v1.begin() + a; it <= v1.begin() + b; it++ )

The code below is a small demonstration

#include <vector>
#include <iostream>

using namespace std;

int main(){

  vector<int> v1;

  for( int i = 0; i < 50; i++ ){
    v1.push_back( 2*(i+1) );
    cout<<v1.at(i)<<"   ";
  }
  cout<<endl;


  vector<int>::iterator it;

  int a = 5;
  int b = 9;

  for( it = v1.begin() + a; it <= v1.begin() + b; it++ ){

    cout<<(*it)<<"   ";
  }
  cout<<endl; 
}

The output is

2   4   6   8   10   12   14   16   18   20   22   24   26   28   30   32   34   36   38   40   42   44   46   48   50   52   54   56   58   60   62   64   66   68   70   72   74   76   78   80   82   84   86   88   90   92   94   96   98   100   
12   14   16   18   20   
2

There's nothing in the standard library to explicitly do this, but if you're willing to use Boost, you can use the following approach using the range concept:

auto range = boost::make_iterator_range(v.begin()+3, v.begin()+7);

BOOST_FOREACH(int i, range)
{
    cout << i << endl;
}

This should output 4567

Baldrick
  • 11,712
  • 2
  • 31
  • 35
-1

You can use a span:

auto first_index = 3;
auto last_index = 6;
auto count = last_index - first_index + 1;
auto interesting_nums = gsl::make_span(std::cbegin(vector) + start_index, count);
for(auto& num : interesting_nums) { /* do stuff */ }

Notes:

  • In your example you got the 4th through the 7th element, i.e. indices 3 through 6 which is what I used here.
  • This piece of code should work for about any container, not just a vector.
  • spans are part of the GSL, support library for the C++ Core Guidelines. They may enter the C++ standard officially in 2020 and are viewed quite favorably by the community.
einpoklum
  • 118,144
  • 57
  • 340
  • 684