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

Viewed 38877

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--;
33 Answers

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

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;

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

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

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";
}

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

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;

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);
}

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, ", "));

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

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

Related