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?
8
  • 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. Commented Apr 19, 2018 at 11:50
  • 1
    New? Uniform Initialisation has been there since C++11 Commented Apr 19, 2018 at 11:52
  • 1
    @KYHSGeekCode: See list initialization. Yes, it's new in C++11 Commented Apr 19, 2018 at 11:52
  • 4
    @MikeVine: You'd be surprised at how many people are not even using C++11 yet :-) Commented Apr 19, 2018 at 11:53
  • 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. Commented Apr 19, 2018 at 12:03

6 Answers 6

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.

Sign up to request clarification or add additional context in comments.

Comments

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?

3 Comments

std::array is worth a mention as it's not evident whether the OP needs a dynamic size.
@underscore_d: I subscribe to the rule "use a std::vector until you can come up with a good reason not to".
...and avoiding dynamic allocation if the size is known at compile-time is a great reason not to.
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

2 Comments

This is using an l as an extra variable that you haven't shown.
You can assign before l=sizeof(arr) or in loop i<sizeof(arr)-1
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.

Comments

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.

Comments

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).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.