Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterating a vector to second to last element with index vs iterator

When iterating from the beginning of a C++11 std::vector to the second to last element, what would be the preferred style?

std::vector<const char*> argv;
std::string str;

Should this kind of more C++-esque method be used

for (const auto& s: decltype(argv)(argv.begin(), argv.end()-1)) {
    str += std::string(s) + ' ';
}

or should the more traditional way be preferred?

for (size_t i = 0; i < argv.size() - 1; ++i) {
    str += std::string(argv[i]);
}
like image 527
chew socks Avatar asked Jul 06 '18 20:07

chew socks


People also ask

How to iterate from the second element of a vector in C++?

This post will discuss how to iterate from the second element of a vector in C++... A simple solution is to get an iterator to the beginning of the vector and use the STL algorithm `std::advance` to advance the iterator by one position. TECHIE DELIGHT </> Ace your Coding Interview PRACTICE FAANG Interview Prep Data Structures &Algorithms

What is endend() iterator in C++?

end () Iterator Method of std::vector method returns an iterator pointing to the theoretical element that follows the last element in the vector. This end () Iterator Method acts as a placeholder; attempting to access it results in undefined behavior.

How do you iterate through a vector with a function?

If it is for iterating from back to front, you can use the reverse iterators ( rbegin , rend ). If it is a function which expects an iterator (for example, because you want it to iterate through all but the last element), then you can use end () - 1 on a vector.

How to update values in a vector without using iterators?

Updating values in vector: For updating values in a vector without using iterators traverse the values stored in vector using reference and updated the value. Below is the syntax for the same: Explanation: Here itr is an address to the value stored in vector which is used to traverse vectors.


2 Answers

Please don't write this:

for (const auto& s: decltype(argv)(argv.begin(), argv.end()-1)) {

First and foremost, nobody (including you) will understand this when you look back on it. Secondly, since decltype(argv) is a vector, this is copying a whole bunch of elements ... all simply because you want to avoid iterating one of them? That's very wasteful.

It also has another problem, which is shared by your second option.

This:

for (size_t i = 0; i < argv.size() - 1; ++i) {

is much more subtly problematic, because size() is unsigned. So if argv happens to be empty, argv.size() - 1 is going to be some enormously large number, and you're actually going to access all these invalid elements of the array leading to undefined behavior.

For iterators, if argv.begin() == argv.end(), then you can't get the previous iterator from end() because there is no previous iterator from end(). All of end() - 1, prev(end()), and --end() are undefined behavior already. At that point, we can't even reason about what the loop will do because we don't even have a valid range.


What I would suggest instead is:

template <typename It>
struct iterator_pair {
    It b, e;

    It begin() const { return b; }
    It end() const { return e; }
};

// this doesn't work for types like raw arrays, I leave that as
// an exercise to the reader
template <typename Range>
auto drop_last(Range& r) 
    -> iterator_pair<decltype(r.begin())>
{
    return {r.begin(), r.begin() == r.end() ? r.end() : std::prev(r.end())};
}

Which lets you do:

for (const auto& s : drop_last(argv)) { ... }

This is efficient (avoids extra copies), avoids undefined behaviors (drop_last() always gives a valid range), and it's pretty clear what it does from the name.

like image 169
Barry Avatar answered Nov 09 '22 02:11

Barry


I find the first option a bit clumsy to read, but as this is rather a matter of personal preference, I propose an alternative approach avoiding a hand-written loop (and under the assumption that argv.size() >= 1), which might be better in the sense that it reduces the likelyhood of typos and indexing bugs.

#include <numeric>

std::string str;

if (!argv.empty())
    str = std::accumulate(argv.begin(), std::prev(argv.end()), str);
like image 24
lubgr Avatar answered Nov 09 '22 01:11

lubgr