Elegantly exclude part of an operation for the last iteration in a loop
Clash Royale CLAN TAG#URR8PPP
up vote
1
down vote
favorite
I'm afraid just about everybody who read this question has misunderstood it. I'm not trying to find the best way of printing a vector. I'm trying to find the best way of excluding part of an operation from the last iteration in a loop. I'm seeking advice about a pattern that is repeated is many different contexts. For this reason, I cannot provide a concrete example.
This question is off-topic
A well-formed comma-separated list has commas after all but the last element. This means that when dealing with these lists, the comma has to be dealt with on all but the last iteration. This is how I do things currently:
for (auto e = vec.cbegin(); e != vec.cend(); ++e)
// code that has to be executed for every element
std::cout << *e;
if (e != vec.cend() - 1)
// code that must not be executed for the last element
std::cout << ", ";
There are five instances of the above pattern in the project that I'm working on. I feel like there might be a more elegant way of doing this. An alternative that I've come up with is this:
// infinite loop if vec is empty
if (!vec.empty())
for (auto e = vec.cbegin(); e != vec.cend() - 1; ++e)
// code that has to be executed for every element
std::cout << *e;
// code that must not be executed for the last element
std::cout << ", ";
// code that has to be executed for every element
std::cout << vec.back();
This alternative is slightly faster because there isn't an if
in the body but that's not a big deal. The if
is true for all but the last iteration so the branch predictor will probably only miss on the last iteration. I also have to repeat code that has to be executed for every element
which means that I need to hoist that into a function.
The first snippet is the best that I can come up with but I feel like this could be done better. Note that writing to std::cout
is just an example.
c++ c++17 iteration
 |Â
show 6 more comments
up vote
1
down vote
favorite
I'm afraid just about everybody who read this question has misunderstood it. I'm not trying to find the best way of printing a vector. I'm trying to find the best way of excluding part of an operation from the last iteration in a loop. I'm seeking advice about a pattern that is repeated is many different contexts. For this reason, I cannot provide a concrete example.
This question is off-topic
A well-formed comma-separated list has commas after all but the last element. This means that when dealing with these lists, the comma has to be dealt with on all but the last iteration. This is how I do things currently:
for (auto e = vec.cbegin(); e != vec.cend(); ++e)
// code that has to be executed for every element
std::cout << *e;
if (e != vec.cend() - 1)
// code that must not be executed for the last element
std::cout << ", ";
There are five instances of the above pattern in the project that I'm working on. I feel like there might be a more elegant way of doing this. An alternative that I've come up with is this:
// infinite loop if vec is empty
if (!vec.empty())
for (auto e = vec.cbegin(); e != vec.cend() - 1; ++e)
// code that has to be executed for every element
std::cout << *e;
// code that must not be executed for the last element
std::cout << ", ";
// code that has to be executed for every element
std::cout << vec.back();
This alternative is slightly faster because there isn't an if
in the body but that's not a big deal. The if
is true for all but the last iteration so the branch predictor will probably only miss on the last iteration. I also have to repeat code that has to be executed for every element
which means that I need to hoist that into a function.
The first snippet is the best that I can come up with but I feel like this could be done better. Note that writing to std::cout
is just an example.
c++ c++17 iteration
2
You're implementing anostream_joiner
- is this reinventing-the-wheel, or do you need something for straight C++17 withoutstd::experimental
?
â Toby Speight
10 hours ago
1
So this is not the actual code you have written or are maintaining? Clearly off-topic, I'm afraid.
â Toby Speight
9 hours ago
1
Damn, this place is hostile
â Kerndog73
9 hours ago
2
@Kerndog73, if you're talking about me, I'm not intending to be hostile, and apologies if you perceive things that way. I think you may well be able to salvage an on-topic question from this, and there's some very worthwhile observations I'd be able to make if I could see an actual context. It really helps to have compilable code - it's worth writing a smallmain()
to exercise a function you've made for your project, for example, and you should definitely include the headers and/or definitions you use. I want this to be a good, on-topic question, and it's frustrating that it's not (yet).
â Toby Speight
9 hours ago
2
@Kerndog73 IâÂÂm honestly confused by the rules as well. This question might be better suited for softwareengineering.stackexchange.com (what a mouthful, that URL). But IâÂÂm not absolutely sure it is, and I believe there should be a room for such a question somewhere.
â Konrad Rudolph
7 hours ago
 |Â
show 6 more comments
up vote
1
down vote
favorite
up vote
1
down vote
favorite
I'm afraid just about everybody who read this question has misunderstood it. I'm not trying to find the best way of printing a vector. I'm trying to find the best way of excluding part of an operation from the last iteration in a loop. I'm seeking advice about a pattern that is repeated is many different contexts. For this reason, I cannot provide a concrete example.
This question is off-topic
A well-formed comma-separated list has commas after all but the last element. This means that when dealing with these lists, the comma has to be dealt with on all but the last iteration. This is how I do things currently:
for (auto e = vec.cbegin(); e != vec.cend(); ++e)
// code that has to be executed for every element
std::cout << *e;
if (e != vec.cend() - 1)
// code that must not be executed for the last element
std::cout << ", ";
There are five instances of the above pattern in the project that I'm working on. I feel like there might be a more elegant way of doing this. An alternative that I've come up with is this:
// infinite loop if vec is empty
if (!vec.empty())
for (auto e = vec.cbegin(); e != vec.cend() - 1; ++e)
// code that has to be executed for every element
std::cout << *e;
// code that must not be executed for the last element
std::cout << ", ";
// code that has to be executed for every element
std::cout << vec.back();
This alternative is slightly faster because there isn't an if
in the body but that's not a big deal. The if
is true for all but the last iteration so the branch predictor will probably only miss on the last iteration. I also have to repeat code that has to be executed for every element
which means that I need to hoist that into a function.
The first snippet is the best that I can come up with but I feel like this could be done better. Note that writing to std::cout
is just an example.
c++ c++17 iteration
I'm afraid just about everybody who read this question has misunderstood it. I'm not trying to find the best way of printing a vector. I'm trying to find the best way of excluding part of an operation from the last iteration in a loop. I'm seeking advice about a pattern that is repeated is many different contexts. For this reason, I cannot provide a concrete example.
This question is off-topic
A well-formed comma-separated list has commas after all but the last element. This means that when dealing with these lists, the comma has to be dealt with on all but the last iteration. This is how I do things currently:
for (auto e = vec.cbegin(); e != vec.cend(); ++e)
// code that has to be executed for every element
std::cout << *e;
if (e != vec.cend() - 1)
// code that must not be executed for the last element
std::cout << ", ";
There are five instances of the above pattern in the project that I'm working on. I feel like there might be a more elegant way of doing this. An alternative that I've come up with is this:
// infinite loop if vec is empty
if (!vec.empty())
for (auto e = vec.cbegin(); e != vec.cend() - 1; ++e)
// code that has to be executed for every element
std::cout << *e;
// code that must not be executed for the last element
std::cout << ", ";
// code that has to be executed for every element
std::cout << vec.back();
This alternative is slightly faster because there isn't an if
in the body but that's not a big deal. The if
is true for all but the last iteration so the branch predictor will probably only miss on the last iteration. I also have to repeat code that has to be executed for every element
which means that I need to hoist that into a function.
The first snippet is the best that I can come up with but I feel like this could be done better. Note that writing to std::cout
is just an example.
c++ c++17 iteration
c++ c++17 iteration
edited 28 mins ago
asked 14 hours ago
Kerndog73
446213
446213
2
You're implementing anostream_joiner
- is this reinventing-the-wheel, or do you need something for straight C++17 withoutstd::experimental
?
â Toby Speight
10 hours ago
1
So this is not the actual code you have written or are maintaining? Clearly off-topic, I'm afraid.
â Toby Speight
9 hours ago
1
Damn, this place is hostile
â Kerndog73
9 hours ago
2
@Kerndog73, if you're talking about me, I'm not intending to be hostile, and apologies if you perceive things that way. I think you may well be able to salvage an on-topic question from this, and there's some very worthwhile observations I'd be able to make if I could see an actual context. It really helps to have compilable code - it's worth writing a smallmain()
to exercise a function you've made for your project, for example, and you should definitely include the headers and/or definitions you use. I want this to be a good, on-topic question, and it's frustrating that it's not (yet).
â Toby Speight
9 hours ago
2
@Kerndog73 IâÂÂm honestly confused by the rules as well. This question might be better suited for softwareengineering.stackexchange.com (what a mouthful, that URL). But IâÂÂm not absolutely sure it is, and I believe there should be a room for such a question somewhere.
â Konrad Rudolph
7 hours ago
 |Â
show 6 more comments
2
You're implementing anostream_joiner
- is this reinventing-the-wheel, or do you need something for straight C++17 withoutstd::experimental
?
â Toby Speight
10 hours ago
1
So this is not the actual code you have written or are maintaining? Clearly off-topic, I'm afraid.
â Toby Speight
9 hours ago
1
Damn, this place is hostile
â Kerndog73
9 hours ago
2
@Kerndog73, if you're talking about me, I'm not intending to be hostile, and apologies if you perceive things that way. I think you may well be able to salvage an on-topic question from this, and there's some very worthwhile observations I'd be able to make if I could see an actual context. It really helps to have compilable code - it's worth writing a smallmain()
to exercise a function you've made for your project, for example, and you should definitely include the headers and/or definitions you use. I want this to be a good, on-topic question, and it's frustrating that it's not (yet).
â Toby Speight
9 hours ago
2
@Kerndog73 IâÂÂm honestly confused by the rules as well. This question might be better suited for softwareengineering.stackexchange.com (what a mouthful, that URL). But IâÂÂm not absolutely sure it is, and I believe there should be a room for such a question somewhere.
â Konrad Rudolph
7 hours ago
2
2
You're implementing an
ostream_joiner
- is this reinventing-the-wheel, or do you need something for straight C++17 without std::experimental
?â Toby Speight
10 hours ago
You're implementing an
ostream_joiner
- is this reinventing-the-wheel, or do you need something for straight C++17 without std::experimental
?â Toby Speight
10 hours ago
1
1
So this is not the actual code you have written or are maintaining? Clearly off-topic, I'm afraid.
â Toby Speight
9 hours ago
So this is not the actual code you have written or are maintaining? Clearly off-topic, I'm afraid.
â Toby Speight
9 hours ago
1
1
Damn, this place is hostile
â Kerndog73
9 hours ago
Damn, this place is hostile
â Kerndog73
9 hours ago
2
2
@Kerndog73, if you're talking about me, I'm not intending to be hostile, and apologies if you perceive things that way. I think you may well be able to salvage an on-topic question from this, and there's some very worthwhile observations I'd be able to make if I could see an actual context. It really helps to have compilable code - it's worth writing a small
main()
to exercise a function you've made for your project, for example, and you should definitely include the headers and/or definitions you use. I want this to be a good, on-topic question, and it's frustrating that it's not (yet).â Toby Speight
9 hours ago
@Kerndog73, if you're talking about me, I'm not intending to be hostile, and apologies if you perceive things that way. I think you may well be able to salvage an on-topic question from this, and there's some very worthwhile observations I'd be able to make if I could see an actual context. It really helps to have compilable code - it's worth writing a small
main()
to exercise a function you've made for your project, for example, and you should definitely include the headers and/or definitions you use. I want this to be a good, on-topic question, and it's frustrating that it's not (yet).â Toby Speight
9 hours ago
2
2
@Kerndog73 IâÂÂm honestly confused by the rules as well. This question might be better suited for softwareengineering.stackexchange.com (what a mouthful, that URL). But IâÂÂm not absolutely sure it is, and I believe there should be a room for such a question somewhere.
â Konrad Rudolph
7 hours ago
@Kerndog73 IâÂÂm honestly confused by the rules as well. This question might be better suited for softwareengineering.stackexchange.com (what a mouthful, that URL). But IâÂÂm not absolutely sure it is, and I believe there should be a room for such a question somewhere.
â Konrad Rudolph
7 hours ago
 |Â
show 6 more comments
5 Answers
5
active
oldest
votes
up vote
11
down vote
accepted
If you are just doing a single-shot printing of a container, just print the first element and then print delim-value pairs. This reduces the iterator requirements to being equality comparable, dereferenceable, and incrementable.
if (!vec.empty()) // invert to return immediately if this is a function
auto first = vec.cbegin();
std::cout << *first;
while (vec.cend() != ++first)
std::cout << ", " << *first;
There is also a stateful approach that reduces the operation strength from branching on whether to print the delimiter to a pointer assignment.
auto actual_delim = ", ";
auto delim = "";
for (const auto& elem : vec)
std::cout << delim << elem;
delim = actual_delim;
See infix_iterator
where this stateful approach is used.
As mentioned in my answer, this approach has the distinct advantage of duplicating the logic of the loop body.
â Konrad Rudolph
7 hours ago
Second version is much more appealing, as we don't lose range-basedfor
(and you can set a function pointer or plainbool
variable instead of a string-pointer if necessary). In a worst case, we could test&elem == &vec.back()
if we can't make the first element have the variant behaviour (e.g. joining a list with" and "
as the final separator).
â Toby Speight
3 hours ago
add a comment |Â
up vote
4
down vote
I'm not sure that elegance should be your first concern here, because the algorithm should be very simple and the code short however you write them.
I/O operations will have a much greater impact on performance than the lay-out of your loop, so you shouldn't worry too much about speed either.
Generality, I believe, is paramount in this case. That's why I see vec.cend() - 1
as the main short-coming of your proposal, because it restricts your algorithm to at least bidirectional iterators, when your goal can be easily achieved for any kind of iterator.
Here's what I would consider better:
#include <iostream>
#include <vector>
template <typename Container, typename Separator> // any container is acceptable...
void pretty_print(const Container& c, const Separator& s)
std::cout << '';
auto it = std::begin(c); // ... even arrays
if (it != std::end(c)) do
std::cout << *it;
if (++it == std::end(c)) break; // that way you don't need to decrement the iterator
std::cout << s;
while (true);
std::cout << '';
I did mention that writing tostd::cout
was just an example but I do like that this works with forward iterators.
â Kerndog73
12 hours ago
I think I have to agree with Calak (answer below): this variant is decidedly less readable than others. But it would help to make it equivalent to what OP is doing to have a direct comparison (i.e. remove the printing of the braces).
â Konrad Rudolph
11 hours ago
add a comment |Â
up vote
3
down vote
I always just use an additional variable:
auto first= true;
for (auto const& x : list)
if (first) first = false; else separator(x);
action(x);
In your case, separator(x)
would be std::cout << ", "
(x
isnâÂÂt actually used), and action(x)
would be std::cout << x
.
I normally abhor mutating variables inside a loop and try to use standard library algorithms on lambdas without side-effects instead. But in this case I believe having an additional boolean flag is simply the most economical, most readable solution.
This is also one of the very few cases (the only?) where IâÂÂm using a single-line if
â¦else
.
The advantage of this method is that you donâÂÂt have to duplicate action(x)
. Even if itâÂÂs just a single, simple expression, having such a duplication makes the logic disjointed and harder to follow.
The disadvantage is that the condition first
has to be checked on every loop iteration. My suspicion is that the CPUâÂÂs branch predictor will handle this extremely well so it shouldnâÂÂt impact performance. Still, itâÂÂs conceptually irksome.
As always IâÂÂd love an explanation for downvotes.
â Konrad Rudolph
7 hours ago
add a comment |Â
up vote
1
down vote
We are getting closer to the off-topic "opinion-based" border
What's readable is a matter of taste. Honestly, i find the @papafaga version less fluent.
You also have the possibility to do the opposite of your second version, to put the particular case before the loop, I do it sometimes when the case is trivial.
template <typename Container, typename Separator>
void pretty_print4(const Container& c, const Separator& s)
if (c.empty()) return;
auto it = std::begin(c);
std::cout << *it;
std::advance(it,1);
while (it != std::end(c))
std::cout << s;
std::cout << *it;
std::advance(it,1);
If you talk about performance, I think all versions are worth. Without optimizations, some versions generate more instructions than others, but once in -O3
, except one or two instructions, it's almost the same. After, if it really matters, you have to look at the machine code and benchmark.
Why usestd::advance
to simply increment by 1? Any iterator hasopertor++
.
â Ruslan
29 mins ago
Two reason: It's more explicit and this
â Calak
7 mins ago
add a comment |Â
up vote
0
down vote
I tried this:
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
template <typename Iterator, typename Char>
std::ostream& write_delim(std::ostream& out, Iterator iter, Iterator end, Char delim)
if (iter != end)
out << *iter++;
for (; iter != end; ++iter)
out << delim << *iter;
return out;
int main(int argc, char** argv)
int result = 0;
typedef std::vector<std::string> string_collection;
string_collection strings;
strings.push_back("alpha");
strings.push_back("beta");
strings.push_back("kappa");
strings.push_back("omega");
write_delim(std::cout, strings.begin(), strings.end(), ',') << std::endl;
write_delim(std::cout, strings.begin(), strings.end(), ", ") << std::endl;
return result;
Output results:
alpha,beta,kappa,omega
alpha, beta, kappa, omega
For me, this:
- Clearly communicates the point of the function, both in its name, and in how the code is written.
- Is generic enough to be used in many situations... notice how I can use either a single character or multiple characters between the strings.
- Works on older compilers.
- Works whether you're writing to a file, console, string, or some customized output stream.
Refinements to this might better communicate errors if you chose the wrong type of delimiter for your output.
New contributor
Welcome to Code Review! Please refrain from answering low-quality questions that are likely to get closed. Once, you've answered, that limits what can be done to improve the question, making it more likely that your efforts are wasted. It's better to wait until the question is properly ready before you answer!
â Toby Speight
10 hours ago
@TobySpeight Why the downvote, and why tell him to refrain from "answering low-quality questions" when all these other non-new contributors are answering as well? Why base your comment and downvote on the fact that he is a new contributor?
â Trevor Jex
1 hour ago
add a comment |Â
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
11
down vote
accepted
If you are just doing a single-shot printing of a container, just print the first element and then print delim-value pairs. This reduces the iterator requirements to being equality comparable, dereferenceable, and incrementable.
if (!vec.empty()) // invert to return immediately if this is a function
auto first = vec.cbegin();
std::cout << *first;
while (vec.cend() != ++first)
std::cout << ", " << *first;
There is also a stateful approach that reduces the operation strength from branching on whether to print the delimiter to a pointer assignment.
auto actual_delim = ", ";
auto delim = "";
for (const auto& elem : vec)
std::cout << delim << elem;
delim = actual_delim;
See infix_iterator
where this stateful approach is used.
As mentioned in my answer, this approach has the distinct advantage of duplicating the logic of the loop body.
â Konrad Rudolph
7 hours ago
Second version is much more appealing, as we don't lose range-basedfor
(and you can set a function pointer or plainbool
variable instead of a string-pointer if necessary). In a worst case, we could test&elem == &vec.back()
if we can't make the first element have the variant behaviour (e.g. joining a list with" and "
as the final separator).
â Toby Speight
3 hours ago
add a comment |Â
up vote
11
down vote
accepted
If you are just doing a single-shot printing of a container, just print the first element and then print delim-value pairs. This reduces the iterator requirements to being equality comparable, dereferenceable, and incrementable.
if (!vec.empty()) // invert to return immediately if this is a function
auto first = vec.cbegin();
std::cout << *first;
while (vec.cend() != ++first)
std::cout << ", " << *first;
There is also a stateful approach that reduces the operation strength from branching on whether to print the delimiter to a pointer assignment.
auto actual_delim = ", ";
auto delim = "";
for (const auto& elem : vec)
std::cout << delim << elem;
delim = actual_delim;
See infix_iterator
where this stateful approach is used.
As mentioned in my answer, this approach has the distinct advantage of duplicating the logic of the loop body.
â Konrad Rudolph
7 hours ago
Second version is much more appealing, as we don't lose range-basedfor
(and you can set a function pointer or plainbool
variable instead of a string-pointer if necessary). In a worst case, we could test&elem == &vec.back()
if we can't make the first element have the variant behaviour (e.g. joining a list with" and "
as the final separator).
â Toby Speight
3 hours ago
add a comment |Â
up vote
11
down vote
accepted
up vote
11
down vote
accepted
If you are just doing a single-shot printing of a container, just print the first element and then print delim-value pairs. This reduces the iterator requirements to being equality comparable, dereferenceable, and incrementable.
if (!vec.empty()) // invert to return immediately if this is a function
auto first = vec.cbegin();
std::cout << *first;
while (vec.cend() != ++first)
std::cout << ", " << *first;
There is also a stateful approach that reduces the operation strength from branching on whether to print the delimiter to a pointer assignment.
auto actual_delim = ", ";
auto delim = "";
for (const auto& elem : vec)
std::cout << delim << elem;
delim = actual_delim;
See infix_iterator
where this stateful approach is used.
If you are just doing a single-shot printing of a container, just print the first element and then print delim-value pairs. This reduces the iterator requirements to being equality comparable, dereferenceable, and incrementable.
if (!vec.empty()) // invert to return immediately if this is a function
auto first = vec.cbegin();
std::cout << *first;
while (vec.cend() != ++first)
std::cout << ", " << *first;
There is also a stateful approach that reduces the operation strength from branching on whether to print the delimiter to a pointer assignment.
auto actual_delim = ", ";
auto delim = "";
for (const auto& elem : vec)
std::cout << delim << elem;
delim = actual_delim;
See infix_iterator
where this stateful approach is used.
answered 12 hours ago
Snowhawk
4,89411028
4,89411028
As mentioned in my answer, this approach has the distinct advantage of duplicating the logic of the loop body.
â Konrad Rudolph
7 hours ago
Second version is much more appealing, as we don't lose range-basedfor
(and you can set a function pointer or plainbool
variable instead of a string-pointer if necessary). In a worst case, we could test&elem == &vec.back()
if we can't make the first element have the variant behaviour (e.g. joining a list with" and "
as the final separator).
â Toby Speight
3 hours ago
add a comment |Â
As mentioned in my answer, this approach has the distinct advantage of duplicating the logic of the loop body.
â Konrad Rudolph
7 hours ago
Second version is much more appealing, as we don't lose range-basedfor
(and you can set a function pointer or plainbool
variable instead of a string-pointer if necessary). In a worst case, we could test&elem == &vec.back()
if we can't make the first element have the variant behaviour (e.g. joining a list with" and "
as the final separator).
â Toby Speight
3 hours ago
As mentioned in my answer, this approach has the distinct advantage of duplicating the logic of the loop body.
â Konrad Rudolph
7 hours ago
As mentioned in my answer, this approach has the distinct advantage of duplicating the logic of the loop body.
â Konrad Rudolph
7 hours ago
Second version is much more appealing, as we don't lose range-based
for
(and you can set a function pointer or plain bool
variable instead of a string-pointer if necessary). In a worst case, we could test &elem == &vec.back()
if we can't make the first element have the variant behaviour (e.g. joining a list with " and "
as the final separator).â Toby Speight
3 hours ago
Second version is much more appealing, as we don't lose range-based
for
(and you can set a function pointer or plain bool
variable instead of a string-pointer if necessary). In a worst case, we could test &elem == &vec.back()
if we can't make the first element have the variant behaviour (e.g. joining a list with " and "
as the final separator).â Toby Speight
3 hours ago
add a comment |Â
up vote
4
down vote
I'm not sure that elegance should be your first concern here, because the algorithm should be very simple and the code short however you write them.
I/O operations will have a much greater impact on performance than the lay-out of your loop, so you shouldn't worry too much about speed either.
Generality, I believe, is paramount in this case. That's why I see vec.cend() - 1
as the main short-coming of your proposal, because it restricts your algorithm to at least bidirectional iterators, when your goal can be easily achieved for any kind of iterator.
Here's what I would consider better:
#include <iostream>
#include <vector>
template <typename Container, typename Separator> // any container is acceptable...
void pretty_print(const Container& c, const Separator& s)
std::cout << '';
auto it = std::begin(c); // ... even arrays
if (it != std::end(c)) do
std::cout << *it;
if (++it == std::end(c)) break; // that way you don't need to decrement the iterator
std::cout << s;
while (true);
std::cout << '';
I did mention that writing tostd::cout
was just an example but I do like that this works with forward iterators.
â Kerndog73
12 hours ago
I think I have to agree with Calak (answer below): this variant is decidedly less readable than others. But it would help to make it equivalent to what OP is doing to have a direct comparison (i.e. remove the printing of the braces).
â Konrad Rudolph
11 hours ago
add a comment |Â
up vote
4
down vote
I'm not sure that elegance should be your first concern here, because the algorithm should be very simple and the code short however you write them.
I/O operations will have a much greater impact on performance than the lay-out of your loop, so you shouldn't worry too much about speed either.
Generality, I believe, is paramount in this case. That's why I see vec.cend() - 1
as the main short-coming of your proposal, because it restricts your algorithm to at least bidirectional iterators, when your goal can be easily achieved for any kind of iterator.
Here's what I would consider better:
#include <iostream>
#include <vector>
template <typename Container, typename Separator> // any container is acceptable...
void pretty_print(const Container& c, const Separator& s)
std::cout << '';
auto it = std::begin(c); // ... even arrays
if (it != std::end(c)) do
std::cout << *it;
if (++it == std::end(c)) break; // that way you don't need to decrement the iterator
std::cout << s;
while (true);
std::cout << '';
I did mention that writing tostd::cout
was just an example but I do like that this works with forward iterators.
â Kerndog73
12 hours ago
I think I have to agree with Calak (answer below): this variant is decidedly less readable than others. But it would help to make it equivalent to what OP is doing to have a direct comparison (i.e. remove the printing of the braces).
â Konrad Rudolph
11 hours ago
add a comment |Â
up vote
4
down vote
up vote
4
down vote
I'm not sure that elegance should be your first concern here, because the algorithm should be very simple and the code short however you write them.
I/O operations will have a much greater impact on performance than the lay-out of your loop, so you shouldn't worry too much about speed either.
Generality, I believe, is paramount in this case. That's why I see vec.cend() - 1
as the main short-coming of your proposal, because it restricts your algorithm to at least bidirectional iterators, when your goal can be easily achieved for any kind of iterator.
Here's what I would consider better:
#include <iostream>
#include <vector>
template <typename Container, typename Separator> // any container is acceptable...
void pretty_print(const Container& c, const Separator& s)
std::cout << '';
auto it = std::begin(c); // ... even arrays
if (it != std::end(c)) do
std::cout << *it;
if (++it == std::end(c)) break; // that way you don't need to decrement the iterator
std::cout << s;
while (true);
std::cout << '';
I'm not sure that elegance should be your first concern here, because the algorithm should be very simple and the code short however you write them.
I/O operations will have a much greater impact on performance than the lay-out of your loop, so you shouldn't worry too much about speed either.
Generality, I believe, is paramount in this case. That's why I see vec.cend() - 1
as the main short-coming of your proposal, because it restricts your algorithm to at least bidirectional iterators, when your goal can be easily achieved for any kind of iterator.
Here's what I would consider better:
#include <iostream>
#include <vector>
template <typename Container, typename Separator> // any container is acceptable...
void pretty_print(const Container& c, const Separator& s)
std::cout << '';
auto it = std::begin(c); // ... even arrays
if (it != std::end(c)) do
std::cout << *it;
if (++it == std::end(c)) break; // that way you don't need to decrement the iterator
std::cout << s;
while (true);
std::cout << '';
answered 12 hours ago
papagaga
3,704221
3,704221
I did mention that writing tostd::cout
was just an example but I do like that this works with forward iterators.
â Kerndog73
12 hours ago
I think I have to agree with Calak (answer below): this variant is decidedly less readable than others. But it would help to make it equivalent to what OP is doing to have a direct comparison (i.e. remove the printing of the braces).
â Konrad Rudolph
11 hours ago
add a comment |Â
I did mention that writing tostd::cout
was just an example but I do like that this works with forward iterators.
â Kerndog73
12 hours ago
I think I have to agree with Calak (answer below): this variant is decidedly less readable than others. But it would help to make it equivalent to what OP is doing to have a direct comparison (i.e. remove the printing of the braces).
â Konrad Rudolph
11 hours ago
I did mention that writing to
std::cout
was just an example but I do like that this works with forward iterators.â Kerndog73
12 hours ago
I did mention that writing to
std::cout
was just an example but I do like that this works with forward iterators.â Kerndog73
12 hours ago
I think I have to agree with Calak (answer below): this variant is decidedly less readable than others. But it would help to make it equivalent to what OP is doing to have a direct comparison (i.e. remove the printing of the braces).
â Konrad Rudolph
11 hours ago
I think I have to agree with Calak (answer below): this variant is decidedly less readable than others. But it would help to make it equivalent to what OP is doing to have a direct comparison (i.e. remove the printing of the braces).
â Konrad Rudolph
11 hours ago
add a comment |Â
up vote
3
down vote
I always just use an additional variable:
auto first= true;
for (auto const& x : list)
if (first) first = false; else separator(x);
action(x);
In your case, separator(x)
would be std::cout << ", "
(x
isnâÂÂt actually used), and action(x)
would be std::cout << x
.
I normally abhor mutating variables inside a loop and try to use standard library algorithms on lambdas without side-effects instead. But in this case I believe having an additional boolean flag is simply the most economical, most readable solution.
This is also one of the very few cases (the only?) where IâÂÂm using a single-line if
â¦else
.
The advantage of this method is that you donâÂÂt have to duplicate action(x)
. Even if itâÂÂs just a single, simple expression, having such a duplication makes the logic disjointed and harder to follow.
The disadvantage is that the condition first
has to be checked on every loop iteration. My suspicion is that the CPUâÂÂs branch predictor will handle this extremely well so it shouldnâÂÂt impact performance. Still, itâÂÂs conceptually irksome.
As always IâÂÂd love an explanation for downvotes.
â Konrad Rudolph
7 hours ago
add a comment |Â
up vote
3
down vote
I always just use an additional variable:
auto first= true;
for (auto const& x : list)
if (first) first = false; else separator(x);
action(x);
In your case, separator(x)
would be std::cout << ", "
(x
isnâÂÂt actually used), and action(x)
would be std::cout << x
.
I normally abhor mutating variables inside a loop and try to use standard library algorithms on lambdas without side-effects instead. But in this case I believe having an additional boolean flag is simply the most economical, most readable solution.
This is also one of the very few cases (the only?) where IâÂÂm using a single-line if
â¦else
.
The advantage of this method is that you donâÂÂt have to duplicate action(x)
. Even if itâÂÂs just a single, simple expression, having such a duplication makes the logic disjointed and harder to follow.
The disadvantage is that the condition first
has to be checked on every loop iteration. My suspicion is that the CPUâÂÂs branch predictor will handle this extremely well so it shouldnâÂÂt impact performance. Still, itâÂÂs conceptually irksome.
As always IâÂÂd love an explanation for downvotes.
â Konrad Rudolph
7 hours ago
add a comment |Â
up vote
3
down vote
up vote
3
down vote
I always just use an additional variable:
auto first= true;
for (auto const& x : list)
if (first) first = false; else separator(x);
action(x);
In your case, separator(x)
would be std::cout << ", "
(x
isnâÂÂt actually used), and action(x)
would be std::cout << x
.
I normally abhor mutating variables inside a loop and try to use standard library algorithms on lambdas without side-effects instead. But in this case I believe having an additional boolean flag is simply the most economical, most readable solution.
This is also one of the very few cases (the only?) where IâÂÂm using a single-line if
â¦else
.
The advantage of this method is that you donâÂÂt have to duplicate action(x)
. Even if itâÂÂs just a single, simple expression, having such a duplication makes the logic disjointed and harder to follow.
The disadvantage is that the condition first
has to be checked on every loop iteration. My suspicion is that the CPUâÂÂs branch predictor will handle this extremely well so it shouldnâÂÂt impact performance. Still, itâÂÂs conceptually irksome.
I always just use an additional variable:
auto first= true;
for (auto const& x : list)
if (first) first = false; else separator(x);
action(x);
In your case, separator(x)
would be std::cout << ", "
(x
isnâÂÂt actually used), and action(x)
would be std::cout << x
.
I normally abhor mutating variables inside a loop and try to use standard library algorithms on lambdas without side-effects instead. But in this case I believe having an additional boolean flag is simply the most economical, most readable solution.
This is also one of the very few cases (the only?) where IâÂÂm using a single-line if
â¦else
.
The advantage of this method is that you donâÂÂt have to duplicate action(x)
. Even if itâÂÂs just a single, simple expression, having such a duplication makes the logic disjointed and harder to follow.
The disadvantage is that the condition first
has to be checked on every loop iteration. My suspicion is that the CPUâÂÂs branch predictor will handle this extremely well so it shouldnâÂÂt impact performance. Still, itâÂÂs conceptually irksome.
edited 11 hours ago
answered 11 hours ago
Konrad Rudolph
4,9111431
4,9111431
As always IâÂÂd love an explanation for downvotes.
â Konrad Rudolph
7 hours ago
add a comment |Â
As always IâÂÂd love an explanation for downvotes.
â Konrad Rudolph
7 hours ago
As always IâÂÂd love an explanation for downvotes.
â Konrad Rudolph
7 hours ago
As always IâÂÂd love an explanation for downvotes.
â Konrad Rudolph
7 hours ago
add a comment |Â
up vote
1
down vote
We are getting closer to the off-topic "opinion-based" border
What's readable is a matter of taste. Honestly, i find the @papafaga version less fluent.
You also have the possibility to do the opposite of your second version, to put the particular case before the loop, I do it sometimes when the case is trivial.
template <typename Container, typename Separator>
void pretty_print4(const Container& c, const Separator& s)
if (c.empty()) return;
auto it = std::begin(c);
std::cout << *it;
std::advance(it,1);
while (it != std::end(c))
std::cout << s;
std::cout << *it;
std::advance(it,1);
If you talk about performance, I think all versions are worth. Without optimizations, some versions generate more instructions than others, but once in -O3
, except one or two instructions, it's almost the same. After, if it really matters, you have to look at the machine code and benchmark.
Why usestd::advance
to simply increment by 1? Any iterator hasopertor++
.
â Ruslan
29 mins ago
Two reason: It's more explicit and this
â Calak
7 mins ago
add a comment |Â
up vote
1
down vote
We are getting closer to the off-topic "opinion-based" border
What's readable is a matter of taste. Honestly, i find the @papafaga version less fluent.
You also have the possibility to do the opposite of your second version, to put the particular case before the loop, I do it sometimes when the case is trivial.
template <typename Container, typename Separator>
void pretty_print4(const Container& c, const Separator& s)
if (c.empty()) return;
auto it = std::begin(c);
std::cout << *it;
std::advance(it,1);
while (it != std::end(c))
std::cout << s;
std::cout << *it;
std::advance(it,1);
If you talk about performance, I think all versions are worth. Without optimizations, some versions generate more instructions than others, but once in -O3
, except one or two instructions, it's almost the same. After, if it really matters, you have to look at the machine code and benchmark.
Why usestd::advance
to simply increment by 1? Any iterator hasopertor++
.
â Ruslan
29 mins ago
Two reason: It's more explicit and this
â Calak
7 mins ago
add a comment |Â
up vote
1
down vote
up vote
1
down vote
We are getting closer to the off-topic "opinion-based" border
What's readable is a matter of taste. Honestly, i find the @papafaga version less fluent.
You also have the possibility to do the opposite of your second version, to put the particular case before the loop, I do it sometimes when the case is trivial.
template <typename Container, typename Separator>
void pretty_print4(const Container& c, const Separator& s)
if (c.empty()) return;
auto it = std::begin(c);
std::cout << *it;
std::advance(it,1);
while (it != std::end(c))
std::cout << s;
std::cout << *it;
std::advance(it,1);
If you talk about performance, I think all versions are worth. Without optimizations, some versions generate more instructions than others, but once in -O3
, except one or two instructions, it's almost the same. After, if it really matters, you have to look at the machine code and benchmark.
We are getting closer to the off-topic "opinion-based" border
What's readable is a matter of taste. Honestly, i find the @papafaga version less fluent.
You also have the possibility to do the opposite of your second version, to put the particular case before the loop, I do it sometimes when the case is trivial.
template <typename Container, typename Separator>
void pretty_print4(const Container& c, const Separator& s)
if (c.empty()) return;
auto it = std::begin(c);
std::cout << *it;
std::advance(it,1);
while (it != std::end(c))
std::cout << s;
std::cout << *it;
std::advance(it,1);
If you talk about performance, I think all versions are worth. Without optimizations, some versions generate more instructions than others, but once in -O3
, except one or two instructions, it's almost the same. After, if it really matters, you have to look at the machine code and benchmark.
answered 11 hours ago
Calak
1,0149
1,0149
Why usestd::advance
to simply increment by 1? Any iterator hasopertor++
.
â Ruslan
29 mins ago
Two reason: It's more explicit and this
â Calak
7 mins ago
add a comment |Â
Why usestd::advance
to simply increment by 1? Any iterator hasopertor++
.
â Ruslan
29 mins ago
Two reason: It's more explicit and this
â Calak
7 mins ago
Why use
std::advance
to simply increment by 1? Any iterator has opertor++
.â Ruslan
29 mins ago
Why use
std::advance
to simply increment by 1? Any iterator has opertor++
.â Ruslan
29 mins ago
Two reason: It's more explicit and this
â Calak
7 mins ago
Two reason: It's more explicit and this
â Calak
7 mins ago
add a comment |Â
up vote
0
down vote
I tried this:
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
template <typename Iterator, typename Char>
std::ostream& write_delim(std::ostream& out, Iterator iter, Iterator end, Char delim)
if (iter != end)
out << *iter++;
for (; iter != end; ++iter)
out << delim << *iter;
return out;
int main(int argc, char** argv)
int result = 0;
typedef std::vector<std::string> string_collection;
string_collection strings;
strings.push_back("alpha");
strings.push_back("beta");
strings.push_back("kappa");
strings.push_back("omega");
write_delim(std::cout, strings.begin(), strings.end(), ',') << std::endl;
write_delim(std::cout, strings.begin(), strings.end(), ", ") << std::endl;
return result;
Output results:
alpha,beta,kappa,omega
alpha, beta, kappa, omega
For me, this:
- Clearly communicates the point of the function, both in its name, and in how the code is written.
- Is generic enough to be used in many situations... notice how I can use either a single character or multiple characters between the strings.
- Works on older compilers.
- Works whether you're writing to a file, console, string, or some customized output stream.
Refinements to this might better communicate errors if you chose the wrong type of delimiter for your output.
New contributor
Welcome to Code Review! Please refrain from answering low-quality questions that are likely to get closed. Once, you've answered, that limits what can be done to improve the question, making it more likely that your efforts are wasted. It's better to wait until the question is properly ready before you answer!
â Toby Speight
10 hours ago
@TobySpeight Why the downvote, and why tell him to refrain from "answering low-quality questions" when all these other non-new contributors are answering as well? Why base your comment and downvote on the fact that he is a new contributor?
â Trevor Jex
1 hour ago
add a comment |Â
up vote
0
down vote
I tried this:
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
template <typename Iterator, typename Char>
std::ostream& write_delim(std::ostream& out, Iterator iter, Iterator end, Char delim)
if (iter != end)
out << *iter++;
for (; iter != end; ++iter)
out << delim << *iter;
return out;
int main(int argc, char** argv)
int result = 0;
typedef std::vector<std::string> string_collection;
string_collection strings;
strings.push_back("alpha");
strings.push_back("beta");
strings.push_back("kappa");
strings.push_back("omega");
write_delim(std::cout, strings.begin(), strings.end(), ',') << std::endl;
write_delim(std::cout, strings.begin(), strings.end(), ", ") << std::endl;
return result;
Output results:
alpha,beta,kappa,omega
alpha, beta, kappa, omega
For me, this:
- Clearly communicates the point of the function, both in its name, and in how the code is written.
- Is generic enough to be used in many situations... notice how I can use either a single character or multiple characters between the strings.
- Works on older compilers.
- Works whether you're writing to a file, console, string, or some customized output stream.
Refinements to this might better communicate errors if you chose the wrong type of delimiter for your output.
New contributor
Welcome to Code Review! Please refrain from answering low-quality questions that are likely to get closed. Once, you've answered, that limits what can be done to improve the question, making it more likely that your efforts are wasted. It's better to wait until the question is properly ready before you answer!
â Toby Speight
10 hours ago
@TobySpeight Why the downvote, and why tell him to refrain from "answering low-quality questions" when all these other non-new contributors are answering as well? Why base your comment and downvote on the fact that he is a new contributor?
â Trevor Jex
1 hour ago
add a comment |Â
up vote
0
down vote
up vote
0
down vote
I tried this:
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
template <typename Iterator, typename Char>
std::ostream& write_delim(std::ostream& out, Iterator iter, Iterator end, Char delim)
if (iter != end)
out << *iter++;
for (; iter != end; ++iter)
out << delim << *iter;
return out;
int main(int argc, char** argv)
int result = 0;
typedef std::vector<std::string> string_collection;
string_collection strings;
strings.push_back("alpha");
strings.push_back("beta");
strings.push_back("kappa");
strings.push_back("omega");
write_delim(std::cout, strings.begin(), strings.end(), ',') << std::endl;
write_delim(std::cout, strings.begin(), strings.end(), ", ") << std::endl;
return result;
Output results:
alpha,beta,kappa,omega
alpha, beta, kappa, omega
For me, this:
- Clearly communicates the point of the function, both in its name, and in how the code is written.
- Is generic enough to be used in many situations... notice how I can use either a single character or multiple characters between the strings.
- Works on older compilers.
- Works whether you're writing to a file, console, string, or some customized output stream.
Refinements to this might better communicate errors if you chose the wrong type of delimiter for your output.
New contributor
I tried this:
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
template <typename Iterator, typename Char>
std::ostream& write_delim(std::ostream& out, Iterator iter, Iterator end, Char delim)
if (iter != end)
out << *iter++;
for (; iter != end; ++iter)
out << delim << *iter;
return out;
int main(int argc, char** argv)
int result = 0;
typedef std::vector<std::string> string_collection;
string_collection strings;
strings.push_back("alpha");
strings.push_back("beta");
strings.push_back("kappa");
strings.push_back("omega");
write_delim(std::cout, strings.begin(), strings.end(), ',') << std::endl;
write_delim(std::cout, strings.begin(), strings.end(), ", ") << std::endl;
return result;
Output results:
alpha,beta,kappa,omega
alpha, beta, kappa, omega
For me, this:
- Clearly communicates the point of the function, both in its name, and in how the code is written.
- Is generic enough to be used in many situations... notice how I can use either a single character or multiple characters between the strings.
- Works on older compilers.
- Works whether you're writing to a file, console, string, or some customized output stream.
Refinements to this might better communicate errors if you chose the wrong type of delimiter for your output.
New contributor
New contributor
answered 10 hours ago
Joseph Van Riper
211
211
New contributor
New contributor
Welcome to Code Review! Please refrain from answering low-quality questions that are likely to get closed. Once, you've answered, that limits what can be done to improve the question, making it more likely that your efforts are wasted. It's better to wait until the question is properly ready before you answer!
â Toby Speight
10 hours ago
@TobySpeight Why the downvote, and why tell him to refrain from "answering low-quality questions" when all these other non-new contributors are answering as well? Why base your comment and downvote on the fact that he is a new contributor?
â Trevor Jex
1 hour ago
add a comment |Â
Welcome to Code Review! Please refrain from answering low-quality questions that are likely to get closed. Once, you've answered, that limits what can be done to improve the question, making it more likely that your efforts are wasted. It's better to wait until the question is properly ready before you answer!
â Toby Speight
10 hours ago
@TobySpeight Why the downvote, and why tell him to refrain from "answering low-quality questions" when all these other non-new contributors are answering as well? Why base your comment and downvote on the fact that he is a new contributor?
â Trevor Jex
1 hour ago
Welcome to Code Review! Please refrain from answering low-quality questions that are likely to get closed. Once, you've answered, that limits what can be done to improve the question, making it more likely that your efforts are wasted. It's better to wait until the question is properly ready before you answer!
â Toby Speight
10 hours ago
Welcome to Code Review! Please refrain from answering low-quality questions that are likely to get closed. Once, you've answered, that limits what can be done to improve the question, making it more likely that your efforts are wasted. It's better to wait until the question is properly ready before you answer!
â Toby Speight
10 hours ago
@TobySpeight Why the downvote, and why tell him to refrain from "answering low-quality questions" when all these other non-new contributors are answering as well? Why base your comment and downvote on the fact that he is a new contributor?
â Trevor Jex
1 hour ago
@TobySpeight Why the downvote, and why tell him to refrain from "answering low-quality questions" when all these other non-new contributors are answering as well? Why base your comment and downvote on the fact that he is a new contributor?
â Trevor Jex
1 hour ago
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207035%2felegantly-exclude-part-of-an-operation-for-the-last-iteration-in-a-loop%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
2
You're implementing an
ostream_joiner
- is this reinventing-the-wheel, or do you need something for straight C++17 withoutstd::experimental
?â Toby Speight
10 hours ago
1
So this is not the actual code you have written or are maintaining? Clearly off-topic, I'm afraid.
â Toby Speight
9 hours ago
1
Damn, this place is hostile
â Kerndog73
9 hours ago
2
@Kerndog73, if you're talking about me, I'm not intending to be hostile, and apologies if you perceive things that way. I think you may well be able to salvage an on-topic question from this, and there's some very worthwhile observations I'd be able to make if I could see an actual context. It really helps to have compilable code - it's worth writing a small
main()
to exercise a function you've made for your project, for example, and you should definitely include the headers and/or definitions you use. I want this to be a good, on-topic question, and it's frustrating that it's not (yet).â Toby Speight
9 hours ago
2
@Kerndog73 IâÂÂm honestly confused by the rules as well. This question might be better suited for softwareengineering.stackexchange.com (what a mouthful, that URL). But IâÂÂm not absolutely sure it is, and I believe there should be a room for such a question somewhere.
â Konrad Rudolph
7 hours ago