Tip: String Wrapper for Formatted String Output in C++

String Wrapper for Formatted String Output in C++

For a long time, I was very reluctant to use “ostrstream,” the C++ equivalent of the “sprintf” C function, because of longer and less understandable code:

ostrstream ss;
ss << "value = " << x;
string s = ss.str();

Using three lines and introducing another (besides string) class seems to me too much and inconsistent with my favorite principle of Occam’s razor.

But relatively recently, I learned (by reading others’ code) that sometimes this extra complexity can be hidden by using macros. For example, a log output macro could be defined in this manner:

#define LOG(arg) { ostringstrean ss; ss << arg;
   output(ss.str()); }

And can be used this way:

LOG("sample");
LOG("value = " << x);

So, I decided to try to implement something similar for just string formatting, without forcing the programmer to use any extra classes or variables, something like this:

String s;
s << "value = " << x;
// then using s for whatever is needed...

Here is the solution: a wrapper around the standard string and ostringstream classes:

//===============================================================
class String : public std::string
{
public:
   // Constructors
   String() {}
   String(const char * s_) : std::string(s_) {}
   String(std::string const & s_) : std::string(s_) {}
   String(const char * s_, int n_) : std::string(s_, n_) {}
   String(const char * s_, int p_, int n_) :
      std::string(s_, p_, n_) {}

   // Converting string to character pointer. Defining this
   // operator creates some danger of bugs caused by dangling
   // pointers, but is very convenient for passing string
   // parameters to "const char *" formal arguments
   operator const char *() { return c_str(); }

   char const & operator [](unsigned long int i_)
        const { return (*(std::string *)this)[i_]; }
   char const & operator [](unsigned int i_)
        const { return (*(std::string *)this)[i_]; }
   char const & operator [](int i_)
        const { return (*(std::string *)this)[i_]; }
   char & operator [](unsigned long int i_)
      { return (*(std::string *)this)[i_]; }
   char & operator [](unsigned int i_)
      { return (*(std::string *)this)[i_]; }
   char & operator [](int i_)
      { return (*(std::string *)this)[i_]; }

   String ToUpper() { String r = *this; for(unsigned int i = 0;
      i < size(); i++) r[i] = ::toupper(r[i]); return r; }
   String ToLower() { String r = *this; for(unsigned int i = 0;
      i < size(); i++) r[i] = ::tolower(r[i]); return r; }
};

template<typename T_> String operator *(String const & s_,
   T_ const & t_)
{
   return s_ + t_;
}

//===============================================================
class StringStream
{
public:

   struct StringStreamRef
   {
      explicit StringStreamRef(StringStream * ss_)
         throw() : ss(ss_) {}
      operator StringStream &() const throw()
      {
         return *ss;
      }
      StringStream * ss;
   };

   explicit StringStream(String & str_) : str(&str_)
   {
      ss = new std::ostringstream;
      *ss << *str;
   }
   explicit StringStream(StringStream & ss_) throw()
   {
      *this = ss_;
   }
   StringStream(StringStreamRef ssr_) throw()
   {
      *this = *ssr_.ss;
   }
   StringStream & operator =(StringStream & ss_) throw()
   {
      ss      = ss_.ss;
      str     = ss_.str;
      ss_.ss  = 0;
      ss_.str = 0;
      return *this;
   }
   ~StringStream()
   {
      if(ss && str)
      *str = ss->str();
      delete ss;
   }

   // Operators for converting last calculated string value to
   // String in case "<<" expression is in rhs.
   operator String() { return ss->str(); }
   template<typename T_> friend StringStream operator
      <<(StringStream ss_, T_ const & p_);
   template<typename T_> friend StringStream operator
      <<(String & s_, T_ const & p_);

   operator StringStreamRef() throw()
   {
      return StringStreamRef(this);
   }

private:
   std::ostringstream * ss;
   String * str;

};

template<typename T_> StringStream operator
   <<(StringStream ss_, T_ const & p_)
{
   *ss_.ss << p_;
   return ss_;
}

template<typename T_> StringStream operator
   <<(String & s_, T_ const & p_)
{
   StringStream w(s_);
   (*w.ss) << p_;
   return w;
}

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read