📜 ⬆️ ⬇️

std :: stringstream and string formatting

Input and output of information is a critical task, without which any program becomes useless. In C ++, input-output streams are traditionally used to solve this problem; they are implemented in the standard IOStream library.

The advantages of this approach are:
- universality: it does not matter what the flow is connected with, - input-output from the console, file, socket, process occurs in the same way;
- transparency: the programmer does not need to explicitly indicate the type of the object being entered or output;
- extensibility: the programmer can add I / O support to the stream for any of its objects, simply by overloading the corresponding operators.

The IOStream library also has a stringstream class, which allows you to associate an I / O stream with a string in memory. Everything that is output to such a stream is added to the end of the line; everything that is read from the stream is retrieved from the beginning of the line.
')
It allows you to do very funny things, for example, to perform type conversion:



 #include <sstream> #include <iostream> int main(int argc, char* argv[]) { std::stringstream ss; ss << "22"; int k = 0; ss >> k; std::cout << k << std::endl; return 0; } 


In addition, this class can be used to format complex strings, for example:

 void func(int id, const std::string& data1, const std::string& data2) { std::stringstream ss; ss << "Operation with id = " << id << " failed, because data1 (" << data1 << ") is incompatible with data2 (" << data2 << ")"; std::cerr << ss.str(); } 


It is clear that in this case the use of stringstream unnecessary, since the message could be output directly to cerr . But what if you want to output the message not to the standard stream, but to use, say, the syslog() function to output the message to the system log? Or, say, to generate an exception containing this string as an explanation:

 void func(int id, const std::string& data1, const std::string& data2) { std::stringstream ss; ss << "Operation with id = " << id << " failed, because data1 (" << data1 << ") is incompatible with data2 (" << data2 << ")"; throw std::runtime_error(ss.str()); } 


It turns out that you need an intermediate stringstream object in which you first form a string, and then get it using the str() method. Agree cumbersome?

C ++ is sometimes blamed for its excessive complexity and redundancy, which, however, in some cases allow you to create very elegant designs. Here and now, templates and the ability to overload operators helped me create a wrapper over stringstream , which allows you to format a string anywhere in the code without additional variables, as if I were just outputting data to a stream:

 void func(int id, const std::string& data1, const std::string& data2) { throw std::runtime_error(MakeString() << "Operation with id = " << id << " failed, because data1 (" << data1 << ") is incompatible with data2 (" << data2 << ")"); } 


Here is the MakeString itself:

 class MakeString { public: template<class T> MakeString& operator<< (const T& arg) { m_stream << arg; return *this; } operator std::string() const { return m_stream.str(); } protected: std::stringstream m_stream; }; 


It works very simply. On the one hand, in the MakeString class, the MakeString operator (<<) is overloaded, which takes a constant reference to an object of any type as an argument, immediately prints this object to its internal stringstream and returns a reference to itself. On the other hand, the conversion operator is overloaded to a string that returns the string formed by stringstream .

Perhaps someone this wrapper will be useful. I will be glad to hear comments, suggestions and suggestions.

Source: https://habr.com/ru/post/131977/


All Articles