38

For example:

for( auto &iter: item_vector ) {
     if(not_on_the_last_element) printf(", ");
}

or

for( auto &iter: skill_level_map ) {
     if(not_on_the_last_element) printf(", ");
}
Barry
  • 286,269
  • 29
  • 621
  • 977
Llamageddon
  • 3,306
  • 4
  • 25
  • 44
  • 1
    Just a note: instead of checking whether you're on the last element, you could move the printing of ", " to the start of the loop body and check if you're on the first element. That's easy to do by setting `bool first = true;` before the loop, and setting `first = false;` inside the loop body. –  Dec 20 '14 at 22:48
  • 1
    Why not use the [`ostream_joiner`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4336.html#iterator.ostream.joiner) from the Fundamentals? – Kerrek SB Dec 20 '14 at 23:01

6 Answers6

74

You can't really. That's kind of the point of range-for, is that you don't need iterators. But you can just change your logic on how you print your comma to print it if it's not first:

bool first = true;
for (auto& elem : item_vector) {
    if (!first) printf(", ");
    // print elem
    first = false;
}

If that's the intent of the loop anyway. Or you could compare the addresses:

for (auto& elem : item_vector) {
    if (&elem != &item_vector.back()) printf(", ");
    // ...
}
Barry
  • 286,269
  • 29
  • 621
  • 977
11

There's no great method. But if we have easy access to the last element of the container...

std::vector<int> item_vector = ...;
for (auto & elem : item_vector) {
    ...
    if (&elem != &item_vector.back())
        printf(", ");
}
Barry
  • 286,269
  • 29
  • 621
  • 977
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
7

These type of loops are best written using the "Loop and a Half" construct:

#include <iostream>
#include <vector>

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

    auto first = begin(somelist), last = end(somelist);
    if (first != last) {                // initial check
        while (true) {
            std::cout << *first++;     
            if (first == last) break;   // check in the middle
            std::cout << ", ";
        }
    }
}

Live Example that prints

1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 6

i.e. without a separator at the end of the last element.

The check in the middle is what makes this different from do-while (check up front) or for_each / range-based for (check at the end). Trying to force a regular for loop on these loops will introduce either extra conditional branches or duplicate program logic.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • How does this entail less conditionals than a check at end for loop? The example on Rosetta Code that you linked to avoids the conditional by iterating to `end(...) - 1` – j b Jun 10 '19 at 06:07
6

This is like a State Pattern.

#include <iostream>
#include <vector>
#include <functional>

int main() {
    std::vector<int> example = {1,2,3,4,5};

    typedef std::function<void(void)> Call;
    Call f = [](){};
    Call printComma = [](){ std::cout << ", "; };
    Call noPrint = [&](){ f=printComma; };
    f = noPrint;

    for(const auto& e:example){
        f();
        std::cout << e;
    }

    return 0;
}


Output:

1, 2, 3, 4, 5

The first time through f points to noPrint which only serves to make f then point to printComma, so commas are only printed before the second and subsequent items.

quamrana
  • 37,849
  • 12
  • 53
  • 71
  • 1
    Brilliant method of avoiding all conditionals! By printing the comma before the number, you avoid the need to check for the last element. By modifying the path of execution on the first loop, you avoid all conditionals. Great solution. It is certainly harder to follow, but it avoids all conditionals. – Gardener Oct 20 '18 at 12:29
  • Why is avoiding conditionals a desired outcome? It seems like this approach trades conditionals for an extra function call and quite a bit of added complexity in the code. I'm also concerned about reusability. – j b Jun 10 '19 at 05:58
  • 1
    Because it *can* take a long time to process conditionals which you don't want to happen in a loop if you can replace it with a single function call. That said, this answer is a somewhat over the top use of the State Pattern. – quamrana Jun 10 '19 at 09:49
  • "somewhat" is an understatement – Silidrone Dec 25 '22 at 10:27
  • What does & mean in the line: Call noPrint = [&](){ f=printComma; }; – Fennekin Jan 28 '23 at 15:12
  • The `&` in `[&]` is a lambda capture, explained [here](https://en.cppreference.com/w/cpp/language/lambda) which means that anything that needs to be known by the lambda is captured `by-reference`. So both `f` and `printComma` are made accessible inside the lambda as references to the corresponding local variables in `main()`. – quamrana Jan 28 '23 at 15:24
  • 1
    A simpler alternative would be `const char* separator = ""; for (const auto& e : v) { std::cout << separator << e; separator = ", "; }`. Might be easier to understand. This can even be put in the C++20 loop-initializer thingy. – Etherealone Apr 01 '23 at 14:41
1

store this code away safely in a header file in your little bag of utilities:

namespace detail {
    template<class Iter>
    struct sequence_emitter
    {
        sequence_emitter(Iter first, Iter last, std::string sep)
        : _first(std::move(first))
        , _last(std::move(last))
        , _sep(std::move(sep))
        {}

        void write(std::ostream& os) const {
            bool first_element = true;
            for (auto current = _first ; current != _last ; ++current, first_element = false)
            {
                if (!first_element)
                    os << _sep;
                os << *current;
            }
        }

    private:
        Iter _first, _last;
        std::string _sep;
    };

    template<class Iter>
    std::ostream& operator<<(std::ostream& os, const sequence_emitter<Iter>& se) {
        se.write(os);
        return os;
    }
}

template<class Iter>
detail::sequence_emitter<Iter>
emit_sequence(Iter first, Iter last, std::string separator = ", ")
{
    return detail::sequence_emitter<Iter>(std::move(first), std::move(last), std::move(separator));
}

then you can emit any range of any container without a trailing separator like this:

vector<int> x { 0, 1, 2, 3, 4, 5 };
cout << emit_sequence(begin(x), end(x)) << endl;

set<string> s { "foo", "bar", "baz" };

cout << emit_sequence(begin(s), end(s), " comes before ") << endl;

expected output:

0, 1, 2, 3, 4, 5
bar comes before baz comes before foo
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
0

Range based for loop are made to iterate over the whole range. If you do not want that, why don't you just do a regular for loop?

auto end = vector.end() - 1;
for (auto iter = vector.begin(); iter != end; ++iter) {
   // do your thing
   printf(", ");
}
// do your thing for the last element

If you do not want to repeat the code twice to "do your thing", as I would, then create a lambda that does it a call it:

auto end = vector.end() - 1;
// create lambda
for (auto iter = vector.begin(); iter != end; ++iter) {
   lambda(*iter);
   printf(", ");
}
lambda(vector.back());