Everyone encounters this issue at some point:
for(const auto& item : items) {
cout << item << separator;
}
... and you get an extra separator you don't want at the end. Sometime it's not printing, but, say, performing some other action, but such that consecutive actions of the same type require some separator action - but the last doesn't.
Now, if you work with old-school for loops and an array, you would do
for(int i = 0; i < num_items; i++)
cout << items[i];
if (i < num_items - 1) { cout << separator; }
}
(or you could special-case the last item out of the loop.) If you have anything that admits non-destructive iterators, even if you don't know its size, you can do:
for(auto it = items.cbegin(); it != items.cend(); it++) {
cout << *it;
if (std::next(it) != items.cend()) { cout << separator; }
}
I dislike the aesthetics of the last two, and like ranged for loops. Can I obtain the same effect as with the last two but using more spiffy C++11ish constructs?
for(const auto& item : items) {
cout << item;
} and_between {
cout << separator;
}
My way (without additional branch) is:
const auto separator = "WhatYouWantHere";
const auto* sep = "";
for(const auto& item : items) {
std::cout << sep << item;
sep = separator;
}
Do you know Duff's device?
int main() {
int const items[] = {21, 42, 63};
int const * item = items;
int const * const end = items + sizeof(items) / sizeof(items[0]);
// the device:
switch (1) {
case 0: do { cout << ", ";
default: cout << *item; ++item; } while (item != end);
}
cout << endl << "I'm so sorry" << endl;
return 0;
}
(Live)
Hopefully I didn't ruin everyone's day. If you don't want to either then never use this.
(mumble) I'm so sorry ...
The device handling empty containers (ranges):
template<typename Iterator, typename Fn1, typename Fn2>
void for_the_device(Iterator from, Iterator to, Fn1 always, Fn2 butFirst) {
switch ((from == to) ? 1 : 2) {
case 0:
do {
butFirst(*from);
case 2:
always(*from); ++from;
} while (from != to);
default: // reached directly when from == to
break;
}
}
Live test:
int main() {
int const items[] = {21, 42, 63};
int const * const end = items + sizeof(items) / sizeof(items[0]);
for_the_device(items, end,
[](auto const & i) { cout << i;},
[](auto const & i) { cout << ", ";});
cout << endl << "I'm (still) so sorry" << endl;
// Now on an empty range
for_the_device(end, end,
[](auto const & i) { cout << i;},
[](auto const & i) { cout << ", ";});
cout << "Incredibly sorry." << endl;
return 0;
}
Excluding an end element from iteration is the sort of thing that Ranges proposal is designed to make easy. (Note that there are better ways to solve the specific task of string joining, breaking an element off from iteration just creates more special cases to worry about, such as when the collection was already empty.)
While we wait for a standardized Ranges paradigm, we can do it with the existing ranged-for with a little helper class.
template<typename T> struct trim_last
{
T& inner;
friend auto begin( const trim_last& outer )
{ using std::begin;
return begin(outer.inner); }
friend auto end( const trim_last& outer )
{ using std::end;
auto e = end(outer.inner); if(e != begin(outer)) --e; return e; }
};
template<typename T> trim_last<T> skip_last( T& inner ) { return { inner }; }
and now you can write
for(const auto& item : skip_last(items)) {
cout << item << separator;
}
Demo: http://rextester.com/MFH77611
For skip_last
that works with ranged-for, a Bidirectional iterator is needed, for similar skip_first
it is sufficient to have a Forward iterator.
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