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]);
}
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
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.
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.
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.
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.
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With