2

I wanted to distinguish between the end of an array an the rest of the elements (in a for loop), but most examples initializes a variable outside the loop, which I think clutters the loop. The shortest example I have achieved is by looking at pointer addresses in a ranged-based for-loop:

for(auto& x : arr){
    cout << x;
    if(&x != &*end(arr)-1)
        cout << ", ";
}

This doesn't need an extra variable, but I am not 100% sure of the implications from using pointers in C++.

A more (or less?) readable example where I initialize a variable in the for-statement, in a way that looks quite intuitive (edit doesn't give portability to fuctions):

for(int i{0}, len{sizeof(arr)/sizeof(*arr)}; i<l; i++){
    cout << arr[i];
    if(i!=len-1)
        cout << ", ";
}
  • Is there a more readable/better/shorter way to do this without extra includes?
  • Are there any cons to these approaches?
dekuShrub
  • 466
  • 4
  • 20
  • 3
    Iterate up to the second last item with a for loop like you have in the second example. Then you can just handle the last element outside the loop as a special case. – AndyG Apr 19 '18 at 11:50
  • Is i{0}, len{sizeof(arr)} 's {} newly introduced operator in C++? – KYHSGeekCode Apr 19 '18 at 11:51
  • 1
    New? Uniform Initialisation has been there since C++11 – Mike Vine Apr 19 '18 at 11:52
  • 1
    @KYHSGeekCode: See [list initialization](http://en.cppreference.com/w/cpp/language/list_initialization). Yes, it's new in C++11 – AndyG Apr 19 '18 at 11:52
  • 4
    @MikeVine: You'd be surprised at how many people are not even using C++11 yet :-) – AndyG Apr 19 '18 at 11:53
  • @CoryKramer typo with the reference, will edit in that now. – dekuShrub Apr 19 '18 at 11:58
  • 1
    Useful rule of thumb: if you don't want the same behaviour for every element in a range, a range loop is not the proper tool. – molbdnilo Apr 19 '18 at 12:03
  • If you are really looking for ways not to print on the last item this answer may be useful: https://stackoverflow.com/questions/22702736/for-loop-prints-an-extra-comma/46438783#46438783 – Galik Apr 19 '18 at 12:14

6 Answers6

3

Why not do the following?

bool not_first_item = false;

for(auto x : arr){
    if (not_first_item) {
       cout << ", ";
       not_first_item = true;
    }
    cout << x;
}

It will print a comma before each item except the first one. It will get the result you require without the need of using complicated pointers.

Ed Heal
  • 59,252
  • 17
  • 87
  • 127
1

If all you have is a pointer to an element in an array, there is no portable way of detecting the position of that element in an array.

Alternatives; best first:

  1. Use a std::vector. That has similar semantics as a plain old array and has the benefit of carrying in the size.

  2. Pass the size of the array as an additional parameter, with size_t type.

  3. Use a magic value to signify the end of an array.

Note that using &x is pointless as x is a value copy. Consider auto& x instead?

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
1

This might help

l=sizeof(arr)
for(int i{0}; i<l-1; i++){
    cout << arr[i];
    cout << ", ";
}
cout << arr[sizeof(arr)];

or

 for(int i{0}; i<sizeof(arr)-1; i++){
        cout << arr[i];
        cout << ", ";
 }
 cout << arr[sizeof(arr)];

there is no extra condition. If showing is the main intention

hkjamil
  • 100
  • 1
  • 8
1

A concise way that I like, without extra branch:

const char* sep = "";
for (const auto& x : arr) {
    std::cout << sep << x;
    sep = ", ";
}

Yes it uses extra variable.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

As a general rule, you should not discard information that you need. When you use a range-based for loop you abstract away the position of an element in the container (and in fact the form of the container). Therefore, it is not the most appropriate tool for the job. You could do this with an index or an iterator because those hold enough information for you to tell whether the element you are iterating over is the last one.

The standard library offers two helpful functions begin and end that either call the member functions begin and end of STL containers, or pointers to the first and past-the-end elements for C-style arrays. Because of the way the end condition is checked, you don't need anything more than a forward iterator.

assert(std::begin(arr) != std::end(arr));
for (auto it = std::begin(arr); it + 1 != std::end(arr); ++it) {
    std::cout << *it << ", ";
}
std::cout << *(std::end(arr) - 1) << '\n';

The above code is fine if you know that you're never going to attempt to print an empty container. Otherwise, you'll need an extra if statement to check for that. Note that even if you have a random access iterator and you use the condition it < std::end(arr) - 1 you might reason that it's fine even for empty arrays, but it is undefined behavior and it might lead to some unexpected bugs when optimizations are turned on.

patatahooligan
  • 3,111
  • 1
  • 18
  • 27
0

Not 100% sure what I was looking for but I think that the real answer might be to check for the starting item like @ed_heel proposed, but I really only needed to change what I checked for in the range-based for-loop to make it much neater:

for(auto &x : arr)
{
    cout << (&x == arr ? "" : ", ") << x;
}

works since arr is an array (a.k.a. pointer in disguise).

dekuShrub
  • 466
  • 4
  • 20