The Artima Developer Community
The C++ Source | C++ Community News | Discuss | Print | Email | Screen Friendly Version | Previous | Next
Sponsored Link

The C++ Source
Stream Thy Strings
by Bjorn Karlsson and Matthew Wilson
May 28, 2005
Summary
In this tutorial, Bjorn and Matthew show the proper use of std::stringstream, and extol the virtues of making your classes streamable.

Often, one finds oneself needing to convert arbitrary types to strings. Be it to present a message to a user, to write information to a log file, or to serialize data for subsequent deserialization, tools for string conversions are kept right at the front of the shed—and because they are used so frequently, these tools rarely tarnish. But perhaps one multipurpose power- tool would suffice?

In this article, we will take a look at how to convert arbitrary types to strings using tools from the C++ Standard Library. For ages, C++ programmers have resorted to unsafe conversions from built-in types using printf. Although we know of exactly two people who never make mistakes with printf (we can give no names, you understand, but Bjorn is definitely not one of them), the rest of the C++ world could use a safer alternative. One such alternative is a Boost library called Boost.Format, which offers type-safe formatting functionality, including formatting of user-defined types. However, in this installment we shall not wander off the premises of the C++ Standard Library.

A Look at ostringstream

In the C++ Standard Library there are a number of stream types; in fact, a separate portion of the Library is called IOStreams[1]. There is much more to be known about the library besides the well-known standard IOStream objects, cin, cout, cerr, clog, and their wide-character companions, wcin, wcout, wcerr, and wclog. Of specific interest for this installment is std::ostringstream, which is actually a typedeffor basic_ostringstream<char>, and is a stream whose underlying storage is in the form of a string (as opposed to an (abstraction of an) input/output device). It is defined in the standard header file called <sstream>, which you will need to include in your applications. You use it like any other stream: you stream things to it just as you would with std::cout. To retrieve the resulting string (of type std::string), you call the member function str(). Here's a simple, but complete, example of how to use it:
#include <sstream>
#include <iostream> 

  int main()
  {
    std::ostringstream stm;
    // Output an int
    stm << 31;
    // Output a char
    stm << ' ';
    // Output a double
    stm << 9.87654;

    // Retrieve the resulting string
    std::cout << stm.str() << '\n';
  }
First, we created an object of type std::ostringstream called stm. Just like with other output streams, we were able to output data to it - here, an int, followed by a char, and finally a float. Extracting the stored string is easy with the ostringstream member function str()— in the example we simply printed that string to std::cout.

For a number of applications, using ostringstreamthis way works like a charm for the conversion problems one encounters. A more practical example than what you just saw would be to mix numeric and text data and store it in a string. Let's assume that you need to format a string that describes the maximum and minimum values for the data type float:

  #include <sstream>
  #include <limits>
  #include <iostream>

  int main()
  {
     std::ostringstream stm;
     stm <<
       "Maximum value for float: " <<
       std::numeric_limits<float>::max() <<
       '\n' << "Minimum value for float: " <<
       std::numeric_limits<float>::min();

     std::string values=stm.str();
  }
In a series of output operations to stm, both text and numeric data is written to the string values and is formatted the way that the user chooses. (for example, values might look like this when running the program:
Maximum value for float: 3.40282e+038
Minimum value for float: 1.17549e-038

Being Manipulative Isn't Always a Bad Thing

There are times when one needs more control over the formatting of the conversions. For example, you may need to convert an integer value to its hexadecimal string representation. This is where manipulators come into play. A manipulator (in the context of IOStreams) is a way of manipulating the behavior of the stream, for example to render output of floating point types in scientific notation. There are a number of predefined manipulators in IOStreams; we shall use the ones for selecting base. To use a manipulator, simply stream it like you would any other data. The following example shows how to switch between decimal, octal, and hexadecimal, output:
  #include <sstream>
  #include <cassert>
  #include <limits>
  #include <iomanip>

  int main()
  {
    std::ostringstream stm;

    // Use a manipulator to select hexadecimal
    // output of integer values
    stm << std::hex;
    // Output an int and a space
    stm << 31 << ' '; // "1f "

    // Use a manipulator to select octal output of integer values
    stm << std::oct;
    // Output an int and a space
    stm << 31 << ' '; // "37 "

    // Use a manipulator to select decimal output of integer values
    stm << std::dec;
    // Output an int and a space
    stm << 31 << ' '; // "31 "

    // Use a manipulator to select hexadecimal
    // output of integer values,
    // and make it uppercase!
    stm << std::hex << std::uppercase << 31 << ' '; // "1F "

    // To avoid confusion, show the base!
    stm << std::showbase <<
      std::hex << 31 << ' ' << // "0X1F"
      std::oct << 31 << ' ' << // "037"
      std::dec << 31; // "31"

    // Retrieve the resulting string
    assert(stm.str()=="1f 37 31 1F 0X1F 037 31");
  }
The example demonstrates how the manipulators std::hex, std::oct, and std::dec, are used. The manipulators are provided for syntactical convenience; it's also possible to achieve the effect of these manipulators by explicitly setting the correct format flags for the stream. For example, the manipulator std::hex performs the equivalent of this code that uses the stream member function setf():
  #include <sstream>
  #include <cassert>
  #include <limits>

  int main()
  {
    std::ostringstream stm;

    // Default output
    stm << 31 << ' '; // "31 "
    // Use a manipulator to select hexadecimal output
    // of integer values
    std::ios_base::fmtflags flags=
      stm.setf(std::ios_base::hex,std::ios_base::basefield);
    // Output an int
    stm << 31; // "1f"
    // Restore the formatting flags
    stm.setf(flags,std::ios_base::basefield);
    stm << ' ' << 31; // " 31"
    assert(stm.str()=="31 1f 31");
  }
As you can see, manipulators offer a syntactically convenient way of manipulating the format flags of streams. Besides a number of predefined manipulators in the IOStreams library, you can define your own and have them work with any kind of stream. We've already seen hex, oct, dec, and uppercase; there are plenty more where those came from [2,3].

Make Your Classes OutputStreamable

We've seen how well the fundamental types support streaming to an ostringstream (other output streams too, of course). But what about user-defined types? How can we make our own classes fit snugly in this scheme? It's actually a straightforward task: we must simply provide an appropriate operator<< for such types. Doing so is often easier than you might think, especially if your classes mainly contain fundamental types and/or C++ Standard Library types. You must decide which information makes sense to output; typically, this is information that represents the current state of an instance of the type. Consider a class, Person, which contains information about a person. It holds a person's first name, last name, age, and gender. Here's how the definition of Person might look:
  enum Gender
  {
    Female,
    Male
  };

  class Person
  {
  public:
    Person(const std::string& firstName,
           const std::string& lastName,
           int age,
           Gender gender)
    : firstName_(firstName),
      lastName_(lastName),
      age_(age),
      gender_(gender) {}

    std::string FirstName() const
    {
      return firstName_;
    }

    std::string LastName() const
    {
      return lastName_;
    }

    int Age() const
    {
      return age_;
    }

    Gender GetGender() const
    {
      return gender_;
    }

  private:
    std::string firstName_;
    std::string lastName_;
    int age_;
    Gender gender_;
  };
With a class like this, all the information required to support output streaming is part of the public interface (through the member functions FirstName(), LastName(), Age(), and GetGender()) , but there's still no direct way of streaming an instance of Person. To have a Person written to std::cout, we could use std::ostringstream like this:
  int main()
  {
    Person matt("Matthew","Wilson",36,Male);
    Person bjorn("Bjorn","Karlsson",31,Male);

    std::cout <<
      "Name: " <<
      matt.FirstName() << " " <<
      matt.LastName()  << ". " <<
      "Age: " <<
      matt.Age() << ". " <<
      "Gender: " << (matt.GetGender()==Female ? "Female" : "Male") << ".\n";

    std::cout <<
      "Name: " <<
      bjorn.FirstName() << " " <<
      bjorn.LastName()  << ". " <<
      "Age: " <<
      bjorn.Age() << ". " <<
      "Gender: " <<
      (bjorn.GetGender()==Female ? "Female" : "Male") << ".\n";
  }
Of course, there are a couple of drawbacks with this approach. First, it is tedious to have to "manually" stream all of the data we're interested in to the stream before retrieving the string value. Second, such a solution is a little too flexible; undoubtedly, we will see slightly different format of the output for Person in different locations of the code—where programmer A thinks that the representation above makes sense, programmer B may decide to change the order of the data, or the captions, or something else—this is unprofessional and unnecessarily hard to maintain.

A better approach is to add intrinsic support for output streaming of Person. We can do this by defining an appropriate operator<<(), like so:

  std::ostream& operator<<(std::ostream& stm, const Person& P)
  {
    stm <<
      "Name: " <<
      P.FirstName() << " " <<
      P.LastName()  << ". " <<
      "Age: " <<
      P.Age() << ". " <<
      "Gender: " <<
      (P.GetGender()==Female ? "Female" : "Male") << ".\n";

    return stm;
  }
The implementation is quite trivial; it simply streams the members of Person to the std::ostream instance stm. By using std::ostream as the type of stream, we ensure that Person supports streaming to almost any type of char-based stream, because std::ostream is a typedef for basic_ostream<char,char_traits<char> > , which is a public base class of all (narrow) standard output streams; to support wide streams we'd need to parameterize the output operator on the character type. (Note: For many classes, operator<<() can be defined as a template, and thus independent of any specific stream types; many of the classes in the STLSoft [3] libraries use this technique to minimize coupling.) Using this augmentation to our Person class, here's how the rewritten example above would look:
  int main()
  {
    Person matt("Matthew","Wilson",36,Male);
    Person bjorn("Bjorn","Karlsson",32,Male);

    std::cout << matt;
    std::cout << bjorn;
  }
Now, that's quite obviously far more succinct, avoids repetitious coding, and works for virtually any type of stream. For example, if we wanted a std::string representation, we could make use of the std::ostringstream class that we've already learned about:
  int main()
  {
    Person matt("Matthew","Wilson",36,Male);
    Person bjorn("Bjorn","Karlsson",32,Male);

    std::ostringstream stm;
    stm << matt;
    std::string MatthewAsString=stm.str();
    stm.str(""); // Clear the stream!
    stm << bjorn;
    std::string BjornAsString=stm.str();

    assert(MatthewAsString == "Name: Matthew Wilson. Age: 36. Gender: Male.\n");
    assert(BjornAsString == "Name: Bjorn Karlsson. Age: 32. Gender: Male.\n");
  }
We could also take things one step further and also make the enumeration Gender support output streaming, which leads to an even simpler implementation of operator<< for Person:
  std::ostream& operator<<(std::ostream& stm, Gender g)
  {
    stm << (g==Female ? "Female" : "Male");
    return stm;
  }

  std::ostream& operator<<(std::ostream& stm, const Person& P)
  {
    stm <<
      "Name: " <<
      P.FirstName() << " " <<
      P.LastName()  << ". " <<
      "Age: " <<
      P.Age() << ". " <<
      "Gender: " <<
      P.GetGender() << ".\n";

  return stm;
}
As you can see, one can quickly reap the benefits of OutputStreamable[4] types. Rather than leaving the chore of performing stream output by hand to clients of your classes, make sure that they play nicely with IOStreams. We intentionally omit the discussion on an important topic here; localization. Different types may have different output depending on the locale (a component that encapsulates cultural-specific information) that is imbued on the stream—simply put, the locale that the stream uses for formatting of culture-specific output. For example, dates have different formats in different countries, and thus, different locales have different ways of outputting dates. Localization is an important, but also complex, topic. We shall talk about localization in a future installment, but for now, you and your classes are free to ignore them (or read up on the subject in [5]).

More Streams at Your Service

We've talked quite a bit about std::ostringstream, but it's not the only string stream in IOStreams; it is complemented by std::istringstream for input, and std::stringstream for both input and output. The following example demonstrates a simple use of istringstream to extract data from a string:
  #include <sstream>
  #include <string>
  #include <cassert>

  int main()
  {
    std::istringstream stm;
    stm.str("1 3.14159265 Two strings");
    int i;
    double d;
    std::string s1,s2;

    stm >> i >> d >> s1 >> s2;

    assert(i==1);
    assert(d==3.14159265);
    assert(s1=="Two");
    assert(s2=="strings");
  }
As you can see in the example, the istringstream is assigned a string that is subsequently used to extract data from; an int, a double, and two strings. By default, istringstream skips whitespace; if you need to change that behavior you can use the manipulator std::noskipws.

The remaining class, std::stringstream, does both input and output streaming. When using these streams, it makes sense to always use the stream with the capabilities you are looking for. This makes it easier to read and understand the code, so if all you need is input (string) streaming, use std::istringstream rather than always going with std::stringstream for convenience. (Remember, what may be convenient at the time of writing may not be convenient when the same code needs to be maintained.)

When Streaming Fails

Note that just like the other streams in IOStreams, the string streams defaults to handling errors by setting the stream state. The following example demonstrates how to detect and handle such errors:
  #include <sstream>
  #include <string>
  #include <cassert>
  #include <iostream>

  int main()
  {
    std::istringstream stm;
    stm >> std::noskipws; // Don't skip whitespace
    stm.str(" 1.23");
    double d;
    stm >> d;
    if (!stm)
    {
      std::cout << "Error streaming to d!\n";
      // Manually fix the problem...assuming we know what went wrong!
      // In this example, we know that we must ignore whitespace,
      // so we simply clear the stream's state.
      stm.clear(std::ios::goodbit);
      // Ignore whitespace!
      stm >> std::skipws;
      stm >> d;
    }

    assert(d==1.23);
}
In the example, the extraction of a double will fail on the first attempt; because there's no way a space can be converted to a double. This leaves the stream in a bad state (std::ios::failbit will be set), which is why the test if (!stm) yields true. Once a stream has gone bad, you must explicitly set it in a good state to be able to use it again. In this example, we know what's gone wrong, and we decide to turn on the skipping of spaces again, which nicely resolves the problem. Then, and only then, can we successfully extract the double!

Checking the stream's state can be tedious and is easy to forget. An alternative is to tell the stream to use exceptions when entering a bad state. This is done through a member function called exceptions, which accepts an argument that denotes which bad states should cause an exception to be thrown. The recommended mask includes the flags badbit and failbit. Here's the above example in a version using exceptions:

  #include <sstream>
  #include <string>
  #include <cassert>
  #include <iostream>

  int main()
  {
    std::istringstream stm;
    stm >> std::noskipws; // Don't skip whitespace
    stm.str(" 1.23");
    double d;
    try
    {
      // Turn on exceptions
      stm.exceptions(std::ios::badbit | std::ios::failbit);
      stm >> d;
    }
    catch(std::ios_base::failure e)
    {
      std::cout << e.what() << '\n';
      // Manually fix it...assuming we know what went wrong!
      stm.clear(std::ios::goodbit);
      // Ignore whitespace!
      stm >> std::skipws;
      stm >> d;
    }

    assert(d==1.23);
  }
Whether to use exceptions or not when streams end up in a bad state largely depends on the problem at hand. Our advice is to consider how a stream in a bad state affects your code, and if failure indicates a truly exceptional situation, then the exception-throwing version is definitely better.

Summary

In this article, we have looked at a suite of very useful classes from IOStreams; std::ostringstream, std::istringstream and std::stringstream. In most cases (and when you're not in need of blistering performance [6,7]), rather than searching for a suitable (specialized) conversion function, this stream type can be used to convert most anything to its string representation. This led us to another important topic, namely enabling your own classes to work seamlessly with output streams by making them OutputStreamable. Finally, we looked at other string stream offerings from the C++ Standard Library. Conversions from various types to strings are ubiquitous in most any application, which means that all the measurements we take to simplify such conversions bring forth excellent value. Input streaming, which was only briefly discussed here, is just as important as output streaming—that will be the topic of a future article.

We hope that the tools covered in this article helps you feel empowered to do what the title says—Stream Thy Strings!

Thank you for reading,
Bjorn Karlsson and Matthew Wilson

Acknowledgements

We would like to thank Kevlin Henney for his helpful comments on the first draft of this article.

Notes and References

  1. Although the wording in the Standard is Input/output library. To add to the terminological ambiguity, the C standard uses the term Streams (C-99: 7.19.2) to refer to the FILE* family of functions.
  2. The C++ Standard Library: A Tutorial and Reference, Nicolai Josuttis (Addison-Wesley, 1999).
  3. The STLSoft libraries provide freely available STL-like extensions, including STL-style wrappers for common operating system and framework APIs (such as Windows, Unix, ACE, ATL, COM, MFC, .NET, XML).
  4. OutputStreamable is the novel appellation for types that support streaming to output streams.
  5. C++ IOStreams and Locales: Advanced Programmer's Guide and Reference, Angelika Langer and Klaus Kreft (Addison-Wesley, 2000).
  6. "Flexible C++ #3: Efficient Integer To String Conversion, part 4", Matthew Wilson, C/C++ Users Journal Experts Forum, January 2004.
  7. Imperfect C++, Matthew Wilson, Addison-Wesley, 2004.

About the Authors

Matthew Wilson is a software development consultant, contributing editor for C/C++ User's Journal, and creator of the STLSoft libraries (http://stlsoft.org/) . He is author of Imperfect C++ (Addison-Wesley, 2004), and is currently working on his next two books, one of which is not about C++. Matthew's appetite for coding challenges is matched only by his appetite for chocolate; he keeps them in check by writing articles and riding his bike (not always at the same time). He can be contacted via http://imperfectcplusplus.com/.

Bjorn Karlsson is proud to be a C++ designer, programmer, teacher, preacher, and student. He has finally learned enough about C++ to realize how little he knows. When not reading or writing articles, books, or code, he has the privilege to be a part of the Boost community, and a member of The C++ Source Advisory Board. His book, Beyond The C++ Standard Library: An Introduction to Boost, will be published by Addison-Wesley in 2005. He appreciates it when people send him interesting emails at bjorn.karlsson@readsoft.com.

Bjorn and Matthew keep a reasonably up-to-date listing of their publications at http://bigboyandrunningbear.com/.

The C++ Source | C++ Community News | Discuss | Print | Email | Screen Friendly Version | Previous | Next

Sponsored Links

Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us