C++11: Range-looping vector from the second element?
Asked Answered
Z

4

11

I have a std::vector<std::string> v; (initialized). How can I use the range-for loop for accessing all elements except the first one (on index zero). For all elements:

for (const string & s: v)
    process(s);

Instead of the v a range expression can be used. How can I write the range expression to skip the first element (or skip the first n elements)?

I know how to get the effect using v.begin() + 1 and using the classic loop. I am searching for the new, more readable, recommended alternative to do that. Possibly something similar to Python slicing? ...like:

for s in v[1:]:
    process(s)
Zendah answered 11/8, 2015 at 8:28 Comment(4)
One way is to make a new vector with the required elements, and then loop through. Or you can use std::for_each: en.cppreference.com/w/cpp/algorithm/for_eachNudd
You can't. The range-based loop isn't the solution for each and every problem. Just use an ordinary loop.Bedard
As mentioned by @GargAnkit you can make a new vector with required elements. You can do this as part of the loop declaration using the vector constructor. So for (auto&& i : std::vector<std::string>(v.begin()+1,v.end())). Demo here. Not as neat as the python example you showed though.Myeshamyhre
See #25653005 which has nice templates for the general problem.Landers
G
7

Create a wrapper for which begin() and end() return the correct iterators and then you can use that as the second argument.

#include <iostream>
#include <vector>

template< typename Collection >
class FromNth
{
    Collection& coll_;
    size_t offset_;

public:
    FromNth( Collection& coll, size_t offset )
        : coll_( coll ), offset_( offset )
    {
    }

    // will nicely resolve to const_iterator if necessary
    auto begin() const -> decltype( coll_.begin() ) 
       { return coll_.begin() + offset_; }

    auto end() const -> decltype( coll_.end() )
       { return coll_.end(); }
};

template< typename Collection >
FromNth<Collection> makeFromNth( Collection& collection, size_t offset )
{
     return FromNth<Collection>( collection, offset );
}

template< typename Collection >
auto begin( const FromNth<Collection> & wrapper ) -> decltype( wrapper.begin() )
{   
   return wrapper.begin();
}

template< typename Collection >
auto end( const FromNth<Collection> & wrapper ) -> decltype( wrapper.end() )
{  
   return wrapper.end();
}

int main()
{
   std::vector< int > coll { 2, 3, 5, 7, 11, 13, 17, 19, 23 };

   for( auto x : makeFromNth( coll, 1 ) )
   {
       std::cout << x << '\n';
   }
   return 0;
}

Note that my fromNth "begin" is undefined behaviour if the size of the input is less than the offset. (If it's equal then it's well defined and begin == end). Therefore do a size check first.

Note: if you are using a recent enough version of boost then iterator_range may already provide you such a "collection" that is similar to my "FromNth".

for( auto const& s : boost::make_iterator_range( v.begin() + 1, v.end() ) )
{
    process( s );
}

Note: the code above worked on CodingGround using C++11 GNU 4.8.3. (That site is very slow though). From C++14 you will not need the ->decltype statements (which are needed in C++11 for templates).

Output:

sh-4.3$ g++ -std=c++11 -o main *.cpp
sh-4.3$ main
3
5
7
11
13
17
19
23 
Glaydsglaze answered 11/8, 2015 at 9:0 Comment(1)
Thanks for the information! I was expecting that there is some std code like this, already (and I could not find) ;)Zendah
M
9

Until ranges make it into the standard library, you won't get any better than a vanilla for loop in plain C++ :

for(auto i = begin(v) + 1, e = end(v); i !=e; ++i)
    // Do something with *i
Madelle answered 11/8, 2015 at 8:56 Comment(8)
won't you? See my answer.Glaydsglaze
@Glaydsglaze as the question is about bending a range-for into shape, I'm assuming plain C++. I've seen enough Boost to know that nothing is impossible if you throw enough templates at it ;)Madelle
boost's iterator_range solves it nicely. Mine is a bit like a way to make an iterator_range but from a numbered offset as the user requested. Of course I can easily rewrite iterator_range too. The user asks if it can be done in C++11 and I am showing how (not just saying use boost)Glaydsglaze
Thanks both for sharing your knowledge. My question was a kind of exploratory. I am rewriting the old code after migrating to VC++ 2015. To summarize, I can let the code as is (using v.begin() + 1). However, I was hoping for the answer of the kind that CashCow presented; so, plus one but the solved goes to CC.Zendah
@Zendah I'll be back when C++ has ranges. *evil laugh*Madelle
@Quentin: Well, you should not chew leek as on your icon (unless you are really that blue). It makes you too evilish (as some vegetarians are) ;) By the way, I like your ++i at the example. Many programmers would write i++.Zendah
@Zendah High-grade leek-chewing blue... uh... things, know how to increment their iterators !Madelle
Yes, I am confused :))))Zendah
G
7

Create a wrapper for which begin() and end() return the correct iterators and then you can use that as the second argument.

#include <iostream>
#include <vector>

template< typename Collection >
class FromNth
{
    Collection& coll_;
    size_t offset_;

public:
    FromNth( Collection& coll, size_t offset )
        : coll_( coll ), offset_( offset )
    {
    }

    // will nicely resolve to const_iterator if necessary
    auto begin() const -> decltype( coll_.begin() ) 
       { return coll_.begin() + offset_; }

    auto end() const -> decltype( coll_.end() )
       { return coll_.end(); }
};

template< typename Collection >
FromNth<Collection> makeFromNth( Collection& collection, size_t offset )
{
     return FromNth<Collection>( collection, offset );
}

template< typename Collection >
auto begin( const FromNth<Collection> & wrapper ) -> decltype( wrapper.begin() )
{   
   return wrapper.begin();
}

template< typename Collection >
auto end( const FromNth<Collection> & wrapper ) -> decltype( wrapper.end() )
{  
   return wrapper.end();
}

int main()
{
   std::vector< int > coll { 2, 3, 5, 7, 11, 13, 17, 19, 23 };

   for( auto x : makeFromNth( coll, 1 ) )
   {
       std::cout << x << '\n';
   }
   return 0;
}

Note that my fromNth "begin" is undefined behaviour if the size of the input is less than the offset. (If it's equal then it's well defined and begin == end). Therefore do a size check first.

Note: if you are using a recent enough version of boost then iterator_range may already provide you such a "collection" that is similar to my "FromNth".

for( auto const& s : boost::make_iterator_range( v.begin() + 1, v.end() ) )
{
    process( s );
}

Note: the code above worked on CodingGround using C++11 GNU 4.8.3. (That site is very slow though). From C++14 you will not need the ->decltype statements (which are needed in C++11 for templates).

Output:

sh-4.3$ g++ -std=c++11 -o main *.cpp
sh-4.3$ main
3
5
7
11
13
17
19
23 
Glaydsglaze answered 11/8, 2015 at 9:0 Comment(1)
Thanks for the information! I was expecting that there is some std code like this, already (and I could not find) ;)Zendah
L
4

C++20 comes with std::ranges::drop_view to achieve this:

// iterate through elements of v skipping the first
for (const auto& s : std::ranges::drop_view{v,1}) {
   process(s);
}
Leffen answered 20/6, 2023 at 1:56 Comment(0)
A
1

You could use Eric Niebler's Range V3 library, and the tail view, that just creates a view of the original container without the first element.

#include <range/v3/view/tail.hpp>

for (const auto& s : ranges::views::tail(v)) {
    process(s);
}
Adahadaha answered 27/12, 2023 at 19:39 Comment(2)
This is very old question. These days, I would probably prefer the std::ranges::drop_view. Anyway, thank you for the information.Zendah
@Zendah I do also prefer the std when possible. But, for example, if you're using GitHub Actions nowadays, you'll find that STL ranges are not implemented everywhere. I think they were included in Clang 15, and GitHub is still using Clang 14.Adahadaha

© 2022 - 2024 — McMap. All rights reserved.