Elegantly exclude part of an operation for the last iteration in a loop

The name of the pictureThe name of the pictureThe name of the pictureClash 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.










share|improve this question



















  • 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
    9 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
    8 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














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.










share|improve this question



















  • 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
    9 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
    8 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












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.










share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 7 mins ago

























asked 13 hours ago









Kerndog73

446213




446213







  • 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
    9 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
    8 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




    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
    9 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
    8 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
9 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
9 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
8 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
8 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










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.






share|improve this answer




















  • 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

















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 << '';






share|improve this answer




















  • 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


















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.






share|improve this answer






















  • As always I’d love an explanation for downvotes.
    – Konrad Rudolph
    7 hours ago


















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.






share|improve this answer




















  • Why use std::advance to simply increment by 1? Any iterator has opertor++.
    – Ruslan
    7 mins ago


















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:



  1. Clearly communicates the point of the function, both in its name, and in how the code is written.

  2. Is generic enough to be used in many situations... notice how I can use either a single character or multiple characters between the strings.

  3. Works on older compilers.

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






share|improve this answer








New contributor




Joseph Van Riper is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

















  • 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
    9 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
    50 mins ago











Your Answer





StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













 

draft saved


draft discarded


















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






























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.






share|improve this answer




















  • 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














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.






share|improve this answer




















  • 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












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.






share|improve this answer












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.







share|improve this answer












share|improve this answer



share|improve this answer










answered 11 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-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
















  • 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















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












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 << '';






share|improve this answer




















  • 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















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 << '';






share|improve this answer




















  • 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













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 << '';






share|improve this answer












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 << '';







share|improve this answer












share|improve this answer



share|improve this answer










answered 12 hours ago









papagaga

3,704221




3,704221











  • 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 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 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











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.






share|improve this answer






















  • As always I’d love an explanation for downvotes.
    – Konrad Rudolph
    7 hours ago















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.






share|improve this answer






















  • As always I’d love an explanation for downvotes.
    – Konrad Rudolph
    7 hours ago













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.






share|improve this answer














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.







share|improve this answer














share|improve this answer



share|improve this answer








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

















  • 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











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.






share|improve this answer




















  • Why use std::advance to simply increment by 1? Any iterator has opertor++.
    – Ruslan
    7 mins ago















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.






share|improve this answer




















  • Why use std::advance to simply increment by 1? Any iterator has opertor++.
    – Ruslan
    7 mins ago













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.






share|improve this answer












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.







share|improve this answer












share|improve this answer



share|improve this answer










answered 11 hours ago









Calak

1,0149




1,0149











  • Why use std::advance to simply increment by 1? Any iterator has opertor++.
    – Ruslan
    7 mins ago

















  • Why use std::advance to simply increment by 1? Any iterator has opertor++.
    – Ruslan
    7 mins ago
















Why use std::advance to simply increment by 1? Any iterator has opertor++.
– Ruslan
7 mins ago





Why use std::advance to simply increment by 1? Any iterator has opertor++.
– Ruslan
7 mins ago











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:



  1. Clearly communicates the point of the function, both in its name, and in how the code is written.

  2. Is generic enough to be used in many situations... notice how I can use either a single character or multiple characters between the strings.

  3. Works on older compilers.

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






share|improve this answer








New contributor




Joseph Van Riper is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

















  • 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
    9 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
    50 mins ago















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:



  1. Clearly communicates the point of the function, both in its name, and in how the code is written.

  2. Is generic enough to be used in many situations... notice how I can use either a single character or multiple characters between the strings.

  3. Works on older compilers.

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






share|improve this answer








New contributor




Joseph Van Riper is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

















  • 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
    9 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
    50 mins ago













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:



  1. Clearly communicates the point of the function, both in its name, and in how the code is written.

  2. Is generic enough to be used in many situations... notice how I can use either a single character or multiple characters between the strings.

  3. Works on older compilers.

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






share|improve this answer








New contributor




Joseph Van Riper is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









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:



  1. Clearly communicates the point of the function, both in its name, and in how the code is written.

  2. Is generic enough to be used in many situations... notice how I can use either a single character or multiple characters between the strings.

  3. Works on older compilers.

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







share|improve this answer








New contributor




Joseph Van Riper is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









share|improve this answer



share|improve this answer






New contributor




Joseph Van Riper is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









answered 10 hours ago









Joseph Van Riper

211




211




New contributor




Joseph Van Riper is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





New contributor





Joseph Van Riper is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






Joseph Van Riper is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











  • 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
    9 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
    50 mins 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
    9 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
    50 mins 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
9 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
9 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
50 mins 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
50 mins ago


















 

draft saved


draft discarded















































 


draft saved


draft discarded














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













































































Comments

Popular posts from this blog

Long meetings (6-7 hours a day): Being “babysat” by supervisor

Is the Concept of Multiple Fantasy Races Scientifically Flawed? [closed]

Confectionery