How can I print a list of elements separated by commas?
Asked Answered
R

36

71

I know how to do this in other languages, but not in C++, which I am forced to use here.

I have a set of strings (keywords) that I'm printing to out as a list, and the strings need a comma between them, but not a trailing comma. In Java, for instance, I would use a StringBuilder and just delete the comma off the end after I've built my string. How can I do it in C++?

auto iter = keywords.begin();
for (iter; iter != keywords.end( ); iter++ )
{
    out << *iter << ", ";
}
out << endl;

I initially tried inserting the following block to do it (moving the comma printing here):

if (iter++ != keywords.end())
    out << ", ";
iter--;
Remarkable answered 16/8, 2010 at 20:18 Comment(2)
I know it makes for a shorter line, but you really should be using for (auto iter = ...; to bind iter to the scope of the loop, unless you explicitly intend to use it afterward.Prod
@πάνταῥεῖ While they are duplicates, why isn't the closure the other way around? This post certainly looks like a better candidate as a dupe target.Astute
A
56

Use an infix_iterator:

// infix_iterator.h 
// 
// Lifted from Jerry Coffin's 's prefix_ostream_iterator 
#if !defined(INFIX_ITERATOR_H_) 
#define  INFIX_ITERATOR_H_ 
#include <ostream> 
#include <iterator> 
template <class T, 
          class charT=char, 
          class traits=std::char_traits<charT> > 
class infix_ostream_iterator : 
    public std::iterator<std::output_iterator_tag,void,void,void,void> 
{ 
    std::basic_ostream<charT,traits> *os; 
    charT const* delimiter; 
    bool first_elem; 
public: 
    typedef charT char_type; 
    typedef traits traits_type; 
    typedef std::basic_ostream<charT,traits> ostream_type; 
    infix_ostream_iterator(ostream_type& s) 
        : os(&s),delimiter(0), first_elem(true) 
    {} 
    infix_ostream_iterator(ostream_type& s, charT const *d) 
        : os(&s),delimiter(d), first_elem(true) 
    {} 
    infix_ostream_iterator<T,charT,traits>& operator=(T const &item) 
    { 
        // Here's the only real change from ostream_iterator: 
        // Normally, the '*os << item;' would come before the 'if'. 
        if (!first_elem && delimiter != 0) 
            *os << delimiter; 
        *os << item; 
        first_elem = false; 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator*() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++(int) { 
        return *this; 
    } 
};     
#endif 

Usage would be something like:

#include "infix_iterator.h"

// ...
std::copy(keywords.begin(), keywords.end(), infix_iterator(out, ","));
Addictive answered 16/8, 2010 at 20:24 Comment(13)
Cool. Like that. Why is there not something like this in boost?Madeleinemadelena
@Martin: because I've never bothered to submit it? I probably should, come to think of it...Addictive
Do submit it. :) You should post to the mailing list and ask if there are any similar iterators someone might want.Septavalent
@GMan: as you might guess from the comment, I also have a prefix_iterator that (obviously enough) puts the delimiter before each item -- but it's true that somebody might easily want something else as well...Addictive
@T.E.D.: In use, it's a quarter the length of the code you posted, and it's considerably more versatile as well (e.g., if you want tab-separated output, that's exactly zero extra work). In short, the extra length is mostly the difference between code that gives a general idea of how the job could be done, and code that's reasonably finished and ready to use.Addictive
@T.E.D.: It has the secondary advantage of presenting exactly the same interface as an std::ostream_iterator, so using it is exactly the same except that it produces the output you actually want.Addictive
En example usage, as the one in the anwser does not work for me. This works copy(elems.begin(), elems.end(), infix_ostream_iterator<string>(cout, ","));Mulciber
this kind of functionality will be available in C++17 with std::experimental::ostream_joiner, and currently works on GCC 6.0-SVN and Clang 3.9-SVN live on Wandbox. See my new answer.Luba
@TemplateRex: Yup--I prefer my name, but such is life, I guess.Addictive
The libstdc++ and libcxx implementations are almost identical to yours. They both skip the delim == 0 check, however. Moreover, I would prefer to use if (!first) *os << delim; else first = false; in order to avoid the repeated assignments.Luba
@TemplateRex: My original design used the same logic. This design was suggested on CodeReview. I was skeptical, but after a little testing became convinced that this was entirely competitive for speed (frequently faster).Addictive
Please clarify that out is in ostream in the usage example.Quezada
@Jerry Quite a complicated solution for a pretty simple problem. I don't argue that having an infix iterator is a neat and nifty thing. But the problem in question, and a whole bunch of problems falling in the same category, can be simply fixed by just adding 3 or 4 lines more to the code provided in the question. Check out my answer below.Terebinthine
L
43

In an experimental C++17 ready compiler coming soon to you, you can use std::experimental::ostream_joiner:

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>

int main()
{
    int i[] = {1, 2, 3, 4, 5};
    std::copy(std::begin(i),
              std::end(i),
              std::experimental::make_ostream_joiner(std::cout, ", "));
}

Live examples using GCC 6.0 SVN and Clang 3.9 SVN

Luba answered 14/3, 2016 at 21:18 Comment(2)
I like this. This is succinct and straightforward. Is there a way to apply a transform without using std::transform as that required creation of intermediate storage?Sam
did this pass experimental status? I couldn't find std::ostream_joiner in any later versionsStigmasterol
T
31

Because everyone has decided to do this with while loops, I'll give an example with for loops.

for (iter = keywords.begin(); iter != keywords.end(); iter++) {
  if (iter != keywords.begin()) cout << ", ";
  cout << *iter;
}
Tollgate answered 16/8, 2010 at 20:29 Comment(4)
This is the canonical container printer loop. If you're tired of writing it each time, we made a magic helper header that does precisely that for all containers. Only comment is that if everything is sufficiently constant and you don't need the iterator after the loop, change the loop head to for (auto iter = keywords.begin(), end = keywords.end(); iter != end; ++iter).Schleicher
Alternatively, if you really want to avoid doing the comparison each time, you can do auto it = keywords.begin(); if (it != keywords.end()) cout << it++; and then run the loop with body cout << ", " << it;. Personally, I prefer to keep everything in one place, though.Schleicher
@Kerrek SB - ...or you could just use a middle-tested loop. Any time you find yourself writing a loop with "on the first (or on the last) iteration do this too" logic, there's a very good chance you have a natural middle-tested loop on your hands.Decal
@TED: What's a "middle-tested loop"?Schleicher
J
26

Assuming a vaguely normal output stream, so that writing an empty string to it does indeed do nothing:

const char *padding = "";
for (auto iter = keywords.begin(); iter != keywords.end(); ++iter) {
    out << padding << *iter;
    padding = ", "
}
Jinks answered 16/8, 2010 at 23:5 Comment(1)
Had occasion to look this question over again two years later, and I rather like this approach. Clever.Decal
C
19

One common approach is to print the first item prior to the loop, and loop only over the remaining items, PRE-printing a comma before each remaining item.

Alternately you should be able to create your own stream that maintains a current state of the line (before endl) and puts commas in the appropriate place.

EDIT: You can also use a middle-tested loop as suggested by T.E.D. It would be something like:

if(!keywords.empty())
{
    auto iter = keywords.begin();
    while(true)
    {
        out << *iter;
        ++iter;
        if(iter == keywords.end())
        {
            break;
        }
        else
        {
            out << ", ";
        }
    }
}

I mentioned the "print first item before loop" method first because it keeps the loop body really simple, but any of the approaches work fine.

Cambist answered 16/8, 2010 at 20:22 Comment(7)
You really should mention the (IMHO better) option of using a middle-tested loop.Decal
You don't need the else clause on your if check, since the true branch breaks your control logic out of the loop anyway. I would have written it more like if (iter == keywords.end()) break;. Also. if you are going to increment something every loop iteration, I find it easier to read if you go ahead and use a for loop and put the iteration in the iteration slot there where people are used to seeing it and know exactly what you are doing.Decal
...of course, when done with all that cleanup, you get essentially my answer below. It does this with five lines of text (six if I add the empty() check properly) where this takes up 17. I think it is much easier to understand too. It puts the iteration in a standard place, and gets rid of two entire levels of nesting around the comma-producing code.Decal
@Decal The reason I didn't use a for loop and put the increment in the third statement is because that won't work: I need to print the item and then increment before doing the end test.Cambist
That's why you put the end test in the middle of the loop. See my answer below.Decal
@Sasha O That's why I had to have the "not empty" check up front to skip the rest of the work in that specific case.Cambist
@MarkB you are correct. Sorry, not sure why I missed this.Polymerous
B
14

There are lots of clever solutions, and too many that mangle the code beyond hope of salvation without letting the compiler do its job.

The obvious solution, is to special-case the first iteration:

bool first = true;
for (auto const& e: sequence) {
   if (first) { first = false; } else { out << ", "; }
   out << e;
}

It's a dead simple pattern which:

  1. Does not mangle the loop: it's still obvious at a glance that each element will be iterated on.
  2. Allows more than just putting a separator, or actually printing a list, as the else block and the loop body can contain arbitrary statements.

It may not be the absolutely most efficient code, but the potential performance loss of a single well-predicted branch is very likely to be overshadowed by the massive behemoth that is std::ostream::operator<<.

Boru answered 14/2, 2016 at 15:11 Comment(1)
This is the way.Pennate
D
7

Something like this?

while (iter != keywords.end())
{
 out << *iter;
 iter++;
 if (iter != keywords.end()) cout << ", ";
}
Donough answered 16/8, 2010 at 20:23 Comment(2)
Not downvoated, but my problem with this solution is that it performs a check on the exact same condition twice every iteration.Decal
Testing the same thing twice is better than testing against both begin and end with every iteration. If the compiler can determine that cout << ", " doesn't change keywords or iter, it can eliminate the second test. If you really want DRY, then use if ( test ) break; or do {} while ( test && cout << ", " ); but those are often considered poor style.Drona
D
6

My typical method for doing separators (in any language) is to use a mid-tested loop. The C++ code would be:

for (;;) {
   std::cout << *iter;
   if (++iter == keywords.end()) break;
   std::cout << ",";
}

(note: An extra if check is needed prior to the loop if keywords may be empty)

Most of the other solutions shown end up doing an entire extra test every loop iteration. You are doing I/O, so the time taken by that isn't a huge problem, but it offends my sensibilities.

Decal answered 16/8, 2010 at 20:49 Comment(8)
The condition doesn't get tested the first time through. That's really a do loop.Drona
@Drona - I suppose that depends on how you chose to define your terms. For me, loops are either top-tested, bottom-tested, or middle tested. This loop is middle-tested. As for implementing it, in C-syntax languages I generally prefer to use for() loops unless it happens to be a special case where one of the other forms (while or do) matches exactly. That's just a matter of taste though.Decal
Speaking of offended sensibilities, you've omitted the one-off tested needed at the start to ensure keywords.size() > 0 or equivalent. This makes your code look simpler than it really is. Sneaky ;-)Jinks
BTW: To get an idea of how rare the need for do is, see closed question https://mcmap.net/q/103775/-39-do-while-39-vs-39-while-39/… .Decal
@Steve Jessop - I said right in the answer that I was doing that. Perhaps it is a little sneaky. However, it is often the case that you know ahead of time that the list won't be empty. Failing that, I generally prefer to test for it with an if (keywords.size() == 0) return;, but of course that only works if used in a specialized print routine that does no other work. Of course, it is my style to write such things. Rather than go into that kind of detail, I just left it out with a note that it might be needed.Decal
Yes, sorry, I must have been skimming and missed that note.Jinks
This code will dereference end in all cases if I'm reading it right.Cambist
@Mark B - Hmmm...I see. Fixing to do it in the order yours did. I take it from your code you prefer to see iter++ on its own line, so feel free to pretend I did that instead. :-) Other permutations are possible too.Decal
M
5

In python we just write:

print ", ".join(keywords)

so why not:

template<class S, class V>
std::string
join(const S& sep, const V& v)
{
  std::ostringstream oss;
  if (!v.empty()) {
    typename V::const_iterator it = v.begin();
    oss << *it++;
    for (typename V::const_iterator e = v.end(); it != e; ++it)
      oss << sep << *it;
  }
  return oss.str();
}

and then just use it like:

cout << join(", ", keywords) << endl;

Unlike in the python example above where the " " is a string and the keywords has to be an iterable of strings, here in this C++ example the separator and keywords can be anything streamable, e.g.

cout << join('\n', keywords) << endl;
Mellissamellitz answered 17/9, 2015 at 7:52 Comment(3)
typename V::const_iterator can even be replaced by auto since C++11.Spiers
I agree, but I tend to keep things compatible with pre-C++11 standard since I've been bitten by old linux installations with old compilers too many times...Mellissamellitz
I think this answer is better than most those answers that having for(...){ if(...){...}else{...} } at least this answer gets rid of a jump instruction in the for loop, which feels lighter and better.Febrific
H
4

to avoid placing an if inside the loop, I use this:

vector<int> keywords = {1, 2, 3, 4, 5};

if (!keywords.empty())
{
    copy(keywords.begin(), std::prev(keywords.end()), 
         std::ostream_iterator<int> (std::cout,", "));
    std::cout << keywords.back();
}

It depends on the vector type, int, but you can remove it with some helper.

Hyperparathyroidism answered 23/8, 2016 at 7:28 Comment(0)
A
4

I suggest you simply switch the first character with the help of a lambda.

std::function<std::string()> f = [&]() {f = [](){ return ","; }; return ""; };                  

for (auto &k : keywords)
    std::cout << f() << k;
Algoid answered 18/8, 2017 at 14:18 Comment(1)
S
4

I think simplicity is better for me, so after I look through all answers I prepared my solution(c++14 required):

#include <iostream>
#include <vector>
#include <utility> // for std::exchange c++14

int main()
{    
    std::vector nums{1, 2, 3, 4, 5}; // c++17
    
    const char* delim = "";
    for (const auto value : nums)
    {
        std::cout << std::exchange(delim, ", ") << value;
    }
}

Output example:

1, 2, 3, 4, 5
Saddle answered 19/8, 2021 at 9:55 Comment(1)
Cool, you can even have this one liner with c++20: for (const char* delim = ""; auto&& v : nums) std::cout << std::exchange(delim, ", ") << v;Vexillum
M
3

Try this:

typedef  std::vector<std::string>   Container;
typedef Container::const_iterator   CIter;
Container   data;

// Now fill the container.


// Now print the container.
// The advantage of this technique is that ther is no extra test during the loop.
// There is only one additional test !test.empty() done at the beginning.
if (!data.empty())
{
    std::cout << data[0];
    for(CIter loop = data.begin() + 1; loop != data.end(); ++loop)
    {
        std::cout << "," << *loop;
    }
}
Madeleinemadelena answered 16/8, 2010 at 20:25 Comment(6)
const_iterator is a distinct, incompatible type from the plain iterator that non-const begin returns.Drona
Although this will compile on platforms where vector::iterator is a simple pointer, confusing breakage may result in debugging mode or changing compilers.Drona
@Potatoswatter: What are you talking about. This will work on all compilers (assuming they are C++ compilers). If you are not modifying the content of a container you should always prefer to use the const_iterator over the iterator.Madeleinemadelena
@Martin: that's what I thought. And then I thought, "where in the standard does it say that std::vector<std::string>::iterator is convertible to std::vector<std::string>::const_iterator? Now I'm worried that you do actually have to cast data to const Container before calling begin() and end().Jinks
@Steve (and Martin): Sorry, that was totally wrong. Table 65 in §23.1 requires that iterator be convertible to const_iterator. I'm just not used to seeing it written like that. (It doesn't have anything to do with overloads, though. const_iterator simply provides a conversion constructor. Usually I try to implement iterator and const_iterator with a single template and disable the undesirable constructor with SFINAE.)Drona
@Steve Jessop: Went down the wrong path before. I will check the standard when I get home. But on the SGI page: sgi.com/tech/stl/Container.html <quote>A conversion from the iterator type to the const iterator type must exist.</quote>Madeleinemadelena
A
3

Another possible solution, which avoids an if

Char comma = '[';
for (const auto& element : elements) {
    std::cout.put(comma) << element;
    comma = ',';
}
std::cout.put(']');

Depends what you're doing in your loop.

Arvell answered 22/7, 2013 at 16:15 Comment(1)
char instead of CharArlina
G
3

If the values are std::strings you can write this nicely in a declarative style with range-v3

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
#include <string>

int main()
{
    using namespace ranges;
    std::vector<std::string> const vv = { "a","b","c" };

    auto joined = vv | view::join(',');

    std::cout << to_<std::string>(joined) << std::endl;
}

For other types which have to be converted to string you can just add a transformation calling to_string.

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
#include <string>

int main()
{
    using namespace ranges;
    std::vector<int> const vv = { 1,2,3 };

    auto joined = vv | view::transform([](int x) {return std::to_string(x);})
                     | view::join(',');
    std::cout << to_<std::string>(joined) << std::endl;
}
Grazynagreabe answered 8/5, 2018 at 20:30 Comment(0)
H
2

There is a little problem with the ++ operator you are using.

You can try:

if (++iter != keywords.end())
    out << ", ";
iter--;

This way, ++ will be evaluated before compare the iterator with keywords.end().

Hazlett answered 16/8, 2010 at 20:23 Comment(0)
T
2

I use a little helper class for that:

class text_separator {
public:
    text_separator(const char* sep) : sep(sep), needsep(false) {}

    // returns an empty string the first time it is called
    // returns the provided separator string every other time
    const char* operator()() {
        if (needsep)
            return sep;
        needsep = true;
        return "";
    }

    void reset() { needsep = false; }

private:
    const char* sep;
    bool needsep;
};

To use it:

text_separator sep(", ");
for (int i = 0; i < 10; ++i)
    cout << sep() << i;
Tayyebeb answered 11/7, 2011 at 14:37 Comment(0)
B
2

Following should do:-

 const std::vector<__int64>& a_setRequestId
 std::stringstream strStream;
 std::copy(a_setRequestId.begin(), a_setRequestId.end() -1, std::ostream_iterator<__int64>(strStream, ", "));
 strStream << a_setRequestId.back();
Bergess answered 28/12, 2016 at 22:33 Comment(1)
.end() - 1 is UB if sequence is emptyOscillator
P
2

I think this variant of @MarkB's answer strikes optimal balance of readability, simplicity and terseness:

auto iter= keywords.begin();
if (iter!=keywords.end()) {
    out << *iter;
    while(++iter != keywords.end())
        out << "," << *iter;
}
out << endl;
Polymerous answered 13/11, 2019 at 18:11 Comment(0)
F
2

It's very easy to fix that (taken from my answer here):

bool print_delim = false;
for (auto iter = keywords.begin(); iter != keywords.end( ); iter++ ) {
    if(print_delim) {
        out << ", ";
    }
    out << *iter;
    print_delim = true;
}
out << endl;

I am using this idiom (pattern?) in many programming languages, and all kind of tasks where you need to construct delimited output from list like inputs. Let me give the abstract in pseudo code:

empty output
firstIteration = true
foreach item in list
    if firstIteration
        add delimiter to output
    add item to output
    firstIteration = false

In some cases one could even omit the firstIteration indicator variable completely:

empty output
foreach item in list
    if not is_empty(output)
        add delimiter to output
    add item to output
Fitzger answered 7/10, 2020 at 20:18 Comment(0)
S
1

I think this should work

while (iter != keywords.end( ))
{

    out << *iter;
    iter++ ;
    if (iter != keywords.end( )) out << ", ";
}
Selfidentity answered 16/8, 2010 at 20:23 Comment(1)
What's going on with this line: ;iter++? Also, this is wrong - you're double appending commas. This would produce word1,,word2,,word3,Tollgate
C
1

Could be like so..

bool bFirst = true;
for (auto curr = keywords.begin();  curr != keywords.end(); ++curr) {
   std::cout << (bFirst ? "" : ", ") << *curr;
   bFirst = false;
}
Cornejo answered 16/8, 2010 at 21:43 Comment(3)
Why conditional and not if?Drona
I like the brevity. I could be convinced otherwise.Cornejo
This one modifies bFirst every time!Schleicher
B
1

Using boost:

std::string add_str("");
const std::string sep(",");

for_each(v.begin(), v.end(), add_str += boost::lambda::ret<std::string>(boost::lambda::_1 + sep));

and you obtain a string containing the vector, comma delimited.

EDIT: to remove the last comma, just issue:

add_str = add_str.substr(0, add_str.size()-1);
Beller answered 5/6, 2012 at 11:33 Comment(0)
F
1

Here are two methods you could use, which are both essentially the same idea. I like these methods because they do not contain any unnecessary conditional checks or assignment operations. I'll call the first one the print first method.

Method 1: the print first method

if (!keywords.empty()) {
    out << *(keywords.begin()); // First element.
    for (auto it = ++(keywords.begin()); it != keywords.end(); it++)
        out << ", " << *it; // Every subsequent element.
}

This is the method I used at first. It works by printing the first element in your container by itself, and then prints every subsequent element preceded by a comma and space. It's simple, concise, and works great if that's all you need it to do. Once you want to do more things, like add an "and" before the last element, this method falls short. You'd have to check each loop iteration for if it's on the last element. Adding a period, or newline after the list wouldn't be so bad, though. You could just add one more line after the for-loop to append whatever you desire to the list.

The second method I like a lot more. That one I'll call the print last method, as it does the same thing as the first but in reverse order.

Method 2: the print last method

if (!keywords.empty()) {
    auto it = keywords.begin(), last = std::prev(keywords.end());
    for (; it != last; it++) // Every preceding element.
        out << *it << ", ";
    out << "and " << *it << ".\n"; // Last element.
}

This one works by printing every element except for the last with a comma and space, allowing you to optionally add an "and" before it, a period after it, and/or a newline character. As you can see, this method gives you a lot more options on how you can handle that last element without affecting the performance of the loop or adding much code.

If it bothers you to leave the first part of the for-loop empty, you could write it like so:

if (!keywords.empty()) {
    auto it, last;
    for (it = keywords.begin(), last = std::prev(keywords.end()); it != last; it++)
        out << *it << ", ";
    out << "and " << *it << ".\n";
}
Foolproof answered 28/11, 2019 at 6:37 Comment(0)
B
1

C++20 brings the formatting library. However as of now (april 2021) neither gcc nor clang implement it yet. But we can use the fmt library on which it is based on:

std::list<int> v{1, 2, 3, 4, 5};
fmt::print("{}", fmt::join(v, ", "));
Beanie answered 21/4, 2021 at 6:45 Comment(0)
P
1

Starting with C++23 you can use std::format or std::print (or std::println) to do this.

#include <print>
#include <vector>

int main() {
    std::print("{}", std::vector{2, 3, 5, 7});
}
[2, 3, 5, 7]

If you need the result as std::string use std::format. Note that formatting ranges requires C++23 support, specifically the implementation of P2286R8 (check C++23 library features).

#include <format>
#include <vector>

int main() {
    std::string text = std::format("{}", std::vector{2, 3, 5, 7});
}

If you are stuck to an older standard, you can use the fmt library to print ranges.

#include <fmt/ranges.h>
#include <vector>

int main() {
    // direct print
    fmt::print("{}", std::vector{2, 3, 5, 7});

    // create std::string object
    std::string str = fmt::format("{}", std::vector{2, 3, 5, 7});
}
Pyromancy answered 17/3, 2023 at 1:15 Comment(0)
D
0

You can use a do loop, rewrite the loop condition for the first iteration, and use the short-circuit && operator and the fact that a valid stream is true.

auto iter = keywords.begin();
if ( ! keywords.empty() ) do {
    out << * iter;
} while ( ++ iter != keywords.end() && out << ", " );
out << endl;
Drona answered 16/8, 2010 at 21:55 Comment(2)
Seems like this would write two commas.Rettarettig
@Michael: woops, copy-paste left in from original code. Fixed.Drona
A
0

I would go with something like this, an easy solution and should work for all iterators.

int maxele = maxele = v.size() - 1;
for ( cur = v.begin() , i = 0; i < maxele ; ++i)
{
    std::cout << *cur++ << " , ";
}
if ( maxele >= 0 )
{
  std::cout << *cur << std::endl;
}
Aubyn answered 16/8, 2010 at 23:14 Comment(0)
A
0

This one overloads the stream operator. Yes global variables are evil.

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>

int index = 0;
template<typename T, template <typename, typename> class Cont>
std::ostream& operator<<(std::ostream& os, const Cont<T, std::allocator<T>>& vec)
{
    if (index < vec.size()) {
        if (index + 1 < vec.size())
            return os << vec[index++] << "-" << vec;
        else
            return os << vec[index++] << vec;
    } else return os;
}

int main()
{
    std::vector<int> nums(10);
    int n{0};
    std::generate(nums.begin(), nums.end(), [&]{ return n++; });
    std::cout << nums << std::endl;
}
Allain answered 18/2, 2014 at 2:28 Comment(0)
C
0

Can use functors:

#include <functional>

string getSeparatedValues(function<bool()> condition, function<string()> output, string separator)
{
    string out;
    out += output();
    while (condition())
        out += separator + output();
    return out;
}

Example:

if (!keywords.empty())
{
    auto iter = keywords.begin();
    cout << getSeparatedValues([&]() { return ++iter != keywords.end(); }, [&]() { return *iter; }, ", ") << endl;
}
Cyclohexane answered 25/10, 2016 at 7:7 Comment(0)
S
0

A combination of c++11 lambda and macro:

#define INFIX_PRINTER(os, sep)([&]()->decltype(os)&{static int f=1;os<<(f?(f=0,""):sep);return os;})()

Usage:

for(const auto& k: keywords)
    INFIX_PRINTER(out, ", ") << k;
Swellfish answered 29/8, 2019 at 7:24 Comment(0)
B
0

I like a range-based for with a is_last_elem test. That imho it's very readable:

for (auto& e : range)
{
    if (!is_last_elem(e, range)) [[likely]] 
        os << e << ", ";
    else
        os << e;
}
os << std::endl;

Full code:

C++20:

#include <iostream>
#include <list>
#include <ranges>
#include <utility>
#include <type_traits>
#include <memory>

template <std::ranges::bidirectional_range R>
bool is_last_elem(const std::ranges::range_value_t<R>& elem, const R& range)
{
    auto last_it = range.end();
    std::advance(last_it, -1);
    return std::addressof(elem) == std::addressof(*last_it);
}

template <std::ranges::bidirectional_range R, class Stream = std::ostream>
void print(const R& range, std::ostream& os = std::cout)
{
    for (auto& e : range)
    {
        if (!is_last_elem(e, range)) [[likely]] 
            os << e << ", ";
        else
            os << e;
    }
    os << std::endl;
}

int main()
{
    std::list<int> v{1, 2, 3, 4, 5};
    print(v);
}

C++17:

#include <iostream>
#include <list>
#include <utility>
#include <type_traits>
#include <memory>

template <class Range>
using value_type_t = std::remove_reference_t<decltype(*std::begin(std::declval<Range>()))>;

template <class Range>
bool is_last_elem(const value_type_t<Range>& elem, const Range& range)
{
    auto last_it = range.end();
    std::advance(last_it, -1);
    return std::addressof(elem) == std::addressof(*last_it);
}

template <class Range, class Stream = std::ostream>
void print(const Range& range, std::ostream& os = std::cout)
{
    for (auto& e : range)
    {
        if (!is_last_elem(e, range))
            os << e << ", ";
        else
            os << e;
    }
    os << std::endl;
}

int main()
{
    std::list<int> v{1, 2, 3, 4, 5};
    print(v);
}
Beanie answered 21/4, 2021 at 6:35 Comment(0)
D
0

Smoothest solution in my opinion:

std::vector<std::string> keywords = { "1", "2", "3", "4", "5" };
std::cout << std::accumulate(std::next(keywords.begin()), keywords.end(), *keywords.begin(),
                     [](const std::string& a, const std::string& b){ return a + ", " + b; }) << std::endl;
Dwyer answered 25/1, 2023 at 21:45 Comment(0)
F
0

I use this:

template<class T>
void print(vector<T> v, ostream& f, const char* separator) {
std::copy(v.begin(), v.end()-1, std::ostream_iterator<T>(f, separator));
f << *(v.end()-1) << endl;}

you call it like this:

print(v, std::cout, ",");
Federalist answered 18/5, 2023 at 13:28 Comment(0)
S
-1

Since C++20, if you are looking for a compact solution and the solution by bolov is not yet supported by your compiler, you can use a range-based for loop with an init-statement for the first flag and a conditional operator as follows:

std::set<std::string> keywords {"these", "are", "my", "keywords"};

for (bool first{true}; auto const& kw : keywords)
    std::cout << (first ? first = false, "" : ", ") << kw;

Output:

are, keywords, my, these

Note: I found this solution in the Example section on this page at cppreference.com.

Code on Wandbox

Sarina answered 21/4, 2021 at 15:11 Comment(0)
R
-1

Since C++11, you can use partition_copy to output conditionally.

std::vector<int> arr{0, 1, 2, 3, 4};

//C++11 example
int count = arr.size();
std::partition_copy(
    arr.begin(), arr.end(),
    std::ostream_iterator<int>(std::cout),
    std::ostream_iterator<int>(std::cout, ", "),
    [count] (int) mutable {
        return (--count) == 0;
    }
);

//after C++14, it support lambda capture initialization
std::partition_copy(
    arr.begin(), arr.end(),
    std::ostream_iterator<int>(std::cout),
    std::ostream_iterator<int>(std::cout, ", "),
    [count = arr.size()] (int) mutable {
        return (--count) == 0;
    }
);

Live Demo

Reproduction answered 10/5, 2022 at 9:50 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.