Printing a comma-separated list in C++

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
1
down vote

favorite












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
    6 hours ago






  • 1




    So this is not the actual code you have written or are maintaining? Clearly off-topic, I'm afraid.
    – Toby Speight
    6 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
    5 hours ago






  • 1




    @TobySpeight It's OK. I'm sorry for saying that. The problem is that I was looking for advice about a pattern that is repeated multiple times in different contexts. That's why I wrote code to print a vector. This question is off-topic.
    – Kerndog73
    5 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
    4 hours ago














up vote
1
down vote

favorite












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
    6 hours ago






  • 1




    So this is not the actual code you have written or are maintaining? Clearly off-topic, I'm afraid.
    – Toby Speight
    6 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
    5 hours ago






  • 1




    @TobySpeight It's OK. I'm sorry for saying that. The problem is that I was looking for advice about a pattern that is repeated multiple times in different contexts. That's why I wrote code to print a vector. This question is off-topic.
    – Kerndog73
    5 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
    4 hours ago












up vote
1
down vote

favorite









up vote
1
down vote

favorite











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















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++ formatting vectors c++17 iteration






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 40 mins ago









200_success

126k14147409




126k14147409










asked 10 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
    6 hours ago






  • 1




    So this is not the actual code you have written or are maintaining? Clearly off-topic, I'm afraid.
    – Toby Speight
    6 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
    5 hours ago






  • 1




    @TobySpeight It's OK. I'm sorry for saying that. The problem is that I was looking for advice about a pattern that is repeated multiple times in different contexts. That's why I wrote code to print a vector. This question is off-topic.
    – Kerndog73
    5 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
    4 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
    6 hours ago






  • 1




    So this is not the actual code you have written or are maintaining? Clearly off-topic, I'm afraid.
    – Toby Speight
    6 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
    5 hours ago






  • 1




    @TobySpeight It's OK. I'm sorry for saying that. The problem is that I was looking for advice about a pattern that is repeated multiple times in different contexts. That's why I wrote code to print a vector. This question is off-topic.
    – Kerndog73
    5 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
    4 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
6 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
6 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
6 hours ago




So this is not the actual code you have written or are maintaining? Clearly off-topic, I'm afraid.
– Toby Speight
6 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
5 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
5 hours ago




1




1




@TobySpeight It's OK. I'm sorry for saying that. The problem is that I was looking for advice about a pattern that is repeated multiple times in different contexts. That's why I wrote code to print a vector. This question is off-topic.
– Kerndog73
5 hours ago




@TobySpeight It's OK. I'm sorry for saying that. The problem is that I was looking for advice about a pattern that is repeated multiple times in different contexts. That's why I wrote code to print a vector. This question is off-topic.
– Kerndog73
5 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
4 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
4 hours ago










5 Answers
5






active

oldest

votes

















up vote
9
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
    4 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
    22 mins 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
    9 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
    7 hours ago


















up vote
1
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
    4 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



























    up vote
    -1
    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
      6 hours 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%2fprinting-a-comma-separated-list-in-c%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
    9
    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
      4 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
      22 mins ago














    up vote
    9
    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
      4 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
      22 mins ago












    up vote
    9
    down vote



    accepted







    up vote
    9
    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 8 hours ago









    Snowhawk

    4,87411027




    4,87411027











    • As mentioned in my answer, this approach has the distinct advantage of duplicating the logic of the loop body.
      – Konrad Rudolph
      4 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
      22 mins ago
















    • As mentioned in my answer, this approach has the distinct advantage of duplicating the logic of the loop body.
      – Konrad Rudolph
      4 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
      22 mins ago















    As mentioned in my answer, this approach has the distinct advantage of duplicating the logic of the loop body.
    – Konrad Rudolph
    4 hours ago




    As mentioned in my answer, this approach has the distinct advantage of duplicating the logic of the loop body.
    – Konrad Rudolph
    4 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
    22 mins 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
    22 mins 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
      9 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
      7 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
      9 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
      7 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 9 hours ago









    papagaga

    3,694221




    3,694221











    • I did mention that writing to std::cout was just an example but I do like that this works with forward iterators.
      – Kerndog73
      9 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
      7 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
      9 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
      7 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
    9 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
    9 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
    7 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
    7 hours ago











    up vote
    1
    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
      4 hours ago















    up vote
    1
    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
      4 hours ago













    up vote
    1
    down vote










    up vote
    1
    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 8 hours ago

























    answered 8 hours ago









    Konrad Rudolph

    4,8911431




    4,8911431











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

















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
















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





    As always I’d love an explanation for downvotes.
    – Konrad Rudolph
    4 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
























      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






















        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 7 hours ago









        Calak

        1,0029




        1,0029




















            up vote
            -1
            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
              6 hours ago














            up vote
            -1
            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
              6 hours ago












            up vote
            -1
            down vote










            up vote
            -1
            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 7 hours ago









            Joseph Van Riper

            111




            111




            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
              6 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
              6 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
            6 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
            6 hours 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%2fprinting-a-comma-separated-list-in-c%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