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