Jonathan Boccara's blog

How to Output Strings Separated by Commas in C++

Published May 7, 2019 - 0 Comments

Every once in a while we all face that problem: how to output strings separated by commas (or by any other character), and not have a comma appear after the last one?

Or rather: how to avoid writing a comma after the last string AND keep the code clean of the annoying bookkeeping that this little operation needs?

This article will show you how to output several strings separated by commas with little burden on your calling code. Now if you have a whole collection (like an STL container for example) of strings to intersperse with comma, the article you want to lok at is Integrating Mutable Curried objects with the STL.

string separated comma C++

Here we’ll also use curried objects. We’ve seen constant curried objects already, that facilitate the dialog between two interface by storing data and translating an interface. And in the case where we want to output strings separated by commas, there is a dialogue between the main application code, that has the strings, and the component that can output those strings (a std::ostream for instance).

But the dialogue is tense. The application code ends up burdened with bookkeeping code to follow what the stream has received already, and whether or not to push the infamous comma.

We can use curried objects, which are facilitators, to simplify the code. But here we’ll need more than a constant curried object. We’re going to use a mutable curried object.

The series on curried object contains:

  • Curried objects – Part 1: Constant curried objects
  • Curried objects – Part 2: How to Output Strings Separated by Commas in C++ (Mutable curried objects)
  • Curried objects – Part 3: Integrating Mutable Curried objects with the STL

Motivating example: tick, tack

string separated comma C++Let’s create a function that prints a certain number of times “tick” and “tack”, interspersed with commas, into an output stream. This output stream could be linked to the console (std::cout), a file (std::ofstream) or even just a std::string (std::ostringstream).

A quick and dirty trial could look like this:

void printTickTack(std::ostream& output, int numberOfTimes)
{
    for (int i = 0; i < numberOfTimes; ++i)
    {
        output << "tick,tack,";
    }
}

It’s quick because it’s short and simple, but it’s dirty because calling the function with printTickTack(std::cout, 3); outputs this:

tick,tack,tick,tack,tick,tack,

Note the trailing comma at the end.

Here is a way to change the code so that it no longer outputs the trailing comma:

void printTickTack2(std::ostream& output, int numberOfTimes)
{
    if (numberOfTimes > 0)
    {
        output << "tick,tack";
    }
    for (int i = 0; i < numberOfTimes - 1; ++i)
    {
        output << ",tick,tack";
    }
}

Which outputs (with the same calling code):

tick,tack,tick,tack,tick,tack

The result is correct, but now it’s the code that has become dirty. The spec is very simple yet the application code is burdened with

  • an additional if statement,
  • two lines of code instead of one that send data to the output,
  • a non-trivial breaking clause for the for loop,
  • an odd string, ",tick, tack", different from the other one "tick,tack", even though the spec does not mention anything about two different strings.

This technical trick makes as much superfluous code in the main application logic for a reader to parse. But on the other hand, the stream cannot take on this complexity because it is a generic component.

Let’s introduce an intermediary object that will help the two talk to each other.

A mutable curried object

Let’s change the above code to introduce a parameter: isFirst, that is true at the first iteration of the loop, and becomes false afterwards. With it, the loop knows whether to output a comma before the "tick, tack":

void printTickTack(std::ostream& output, int numberOfTimes)
{
    bool isFirst = true;
    for (int i = 0; i < numberOfTimes; ++i)
    {
        if (isFirst)
        {
            isFirst = false;
        }
        else
        {
            output << ',';
        }
        output << "tick,tack";
    }
}

Let’s try out the code with printTickTack(std::cout, 3);:

tick,tack,tick,tack,tick,tack

The result is still correct but, if anything, the code has become worse than before. Now there is an if statement inside the loop and a boolean variable to keep in mind while reading the application code.

However, we can extract a function out of this code, parametrised with isFirst and the string to output:

void printSeparatedByComma(std::string const& value, std::ostream& output, bool& isFirst)
{
    if (isFirst)
    {
        isFirst = false;
    }
    else
    {
        output << ',';
    }
    output << value;
}

void printTickTack(std::ostream& output, int numberOfTimes)
{
    bool isFirst = true;
    for (int i = 0; i < numberOfTimes; ++i)
    {
        printSeparatedByComma("tick,tack", output, isFirst);
    }
}

It’s not ideal since printSeparatedByComma operates on isFirst which is outside of its scope, but on the other hand most of the complexity has gone to that new function.

An interesting consequence is that we can totally remove the comma delimiter from the calling code. Indeed, the following code outputs the same result:

void printTickTack(std::ostream& output, int numberOfTimes)
{
    bool isFirst = true;
    for (int i = 0; i < numberOfTimes; ++i)
    {
        printSeparatedByComma("tick", output, isFirst);
        printSeparatedByComma("tack", output, isFirst);
    }
}

The calling code looks better, however there are at least two issues left with it:

  • it still shows the technical variable isFirst,
  • the function printSeparatedByComma is called several times with the same argument.

To facilitate the dialogue between printTickTack and printSeparatedByComma, let’s introduce a curried object, that will take care of the two fixed parameters output and isFirst:

class CSVPrinter
{
public:
    explicit CSVPrinter(std::ostream& output) : output_(output), isFirst_(true) {}
    
    friend CSVPrinter& operator<<(CSVPrinter& csvPrinter, std::string const& value)
    {
        if (csvPrinter.isFirst_)
        {
            csvPrinter.isFirst_ = false;
        }
        else
        {
            csvPrinter.output_ << ',';
        }
    
        csvPrinter.output_ << value;
        return csvPrinter;
    }
private:
    std::ostream& output_;
    bool isFirst_;
};

We implement an operator<< to give it a stream-like interface.

Now the calling code becomes much simpler:

void printTickTack(std::ostream& output, int numberOfTimes)
{
    CSVPrinter csvPrinter{output};
    for (int i = 0; i < numberOfTimes; ++i)
    {
        csvPrinter << "tick";
        csvPrinter << "tack";
    }
}

No more bookkeeping in the application code, an not even a trace of a comma any more. We could easily parametrize the CSVPrinter to accept another delimiter than a comma.

Discussion

The effect of introducing the curried object has made the calling code nearly as simple as its specification, which is a good thing. This curried object is mutable in the sense that some of its members (here, isFirst) are not const and are designed to change in the course of its life.

Now is mutable state a good thing? Indeed, mutable state is at the origin of some bugs when it is not in the state we expect it to be (which is why the functional programming paradigm forbids mutable state). In our case though, the operation itself has some complexity, and it is better off in an encapsulated object with a clear interface rather than as a wart on the main application logic.

Another issue with mutable state is multithreading. Indeed, a shared mutable state is not easy to handle when several threads have access to it. In our case, even if the above component could be modified to be thread-safe (likely at the expense of performance), the above version helps simplifying a local piece of code that needs to build a string separated by commas.

Finding an elegant name

In his paper Arguments and Results, James Noble introduces a mutable curried object with the interface of a word processor, to which a client code can ask to write a piece of text at a given position and with a given font.

A call to the interface (which is in SmallTalk) looks like this:

view drawString: 'This is an example' at: origin font: font.

The initial problem with this interface is that

  • if we want to write several pieces of text with the same font, which is a common case, we have to pass the font every time,
  • each time we want to write a piece of text we have to work out the position to write at, and it depends on the words we have written before.

The article proposes to introduce a curried object in much the same vein as our CSVPrinter, that takes the font once, and computes every incremental position so that its client code only has to send it the next piece of text.

But the beautiful thing about the curried object in James’s article is its name: Pen.

In three letters, the interface explains its usage in an intuitive manner, by referring to a concept that we already know. To write a word, we pick up a pen, write the word, and put the pen down. Then to write another word, we pick it up again and write the new word. And so on.

string separated comma C++

Compared to “Pen“, the name  of our CSVPrinter seems pretty crappy now. Isn’t there a concept that our curried object models, and that could provide a better inspiration for its name?

Perhaps one possibility would be to name it CSVTypewriter. Indeed, the CSV writer doesn’t work the same way as the word processor. In the word processor, the pen goes to the next line whenever there is more than enough text to fill a line. A CSV line however, can be arbitrarily long: it is only a specific action on the interface that can break it off. Just like a typewriter, where the writer needs to pull a lever to slide the carriage back to the left.

string separated comma C++

But this could be over the top, and maybe there is a more adapted analogy. As usual, your opinions are welcome.

Anyway, this idea of a typewriter made me realize that, whichever the name of our helper, it would make sense to add to it a method to go to the next line:

#include <iostream>

class CSVPrinter
{
public:
    void nextLine()
    {
        output_ << '\n';
        isFirst_ = true;
    }
    
    // ...
};

Here is a full code example that uses this methods along with the others:

#include <iostream>

class CSVPrinter
{
public:
    explicit CSVPrinter(std::ostream& output) : output_(output), isFirst_(true) {}
    void nextLine()
    {
        output_ << '\n';
        isFirst_ = true;
    }
    
    friend CSVPrinter& operator<<(CSVPrinter& csvPrinter, std::string const& value)
    {
        if (csvPrinter.isFirst_)
        {
            csvPrinter.isFirst_ = false;
        }
        else
        {
            csvPrinter.output_ << ',';
        }
    
        csvPrinter.output_ << value;
        return csvPrinter;
    }
private:
    std::ostream& output_;
    bool isFirst_;
};

void printTickTack(CSVPrinter& csvPrinter, int numberOfTimes)
{
    for (int i = 0; i < numberOfTimes; ++i)
    {
        csvPrinter << "tick";
        csvPrinter << "tack";
    }
}

int main()
{
    CSVPrinter csvPrinter{std::cout};
    
    printTickTack(csvPrinter, 3);
    csvPrinter.nextLine();
    printTickTack(csvPrinter, 4);
}

And this code outputs:

tick,tack,tick,tack,tick,tack
tick,tack,tick,tack,tick,tack,tick,tack

Can a STL algorithm send data to a curried object?

The loop we have used here to demonstrate the concept of a mutable curried object was very simple.

What if we had more complex loops over collections, such as those in the STL algorithms? How do we integrate curried objects with them?

Stay tuned, as this is the topic of the 3rd episode in our series on Curried objects coming up!

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin