My own implementation of make_string() as proposed here by Arkadiy:
class make_string
{
public:
template <typename T>
make_string& operator<<( T const & datum )
{
buffer_ << datum;
return *this;
}
operator std::string () const
{
return buffer_.str();
}
private:
std::ostringstream buffer_;
};
// usage:
void f( std::string const & );
int main()
{
std::string name = "David";
f( make_string() << "Hello " << name << "!" );
}
j_random_hacker suggests adding a const char* conversion to the class above so that it can be used with legacy/C libraries that take null terminated strings. I have had not that much time to think about it, but I feel unease as adding that conversion would allow the following code to compile [edit: bad example, read answer to second comment]:
const char* str = make_string() << "Hello world"; // 1
// ...
std::cout << str << std::endl; // 2
To the compiler the code above is correct, but the usage is wrong as in line 1 the make_string() temporary is destroyed and the pointer is no longer valid.
Response to second comment:
Assume a simple implementation of const char* operator:
class make_string
{
// ...
public:
operator const char* ()
{
return buffer_.str().c_str();
}
};
The str() creates a std::string object, whose scope is inside the operator const char_ method. The c_str() returns a pointer inside that std::string. By the time the caller receives the pointer, the std::string has gone out of scope and the memory has been released.
The other big difference with the code sample where the user requests the .c_str() from the std::string returned from the conversion operator is that the user is explicitly doing it and as such it appears in the code. If the conversion to null terminated string is implemented the user will have more trouble to detect where the error is.
void f( std::string const & );
void g( const char* );
f( make_string() << "Say hi" ); // works ok
g( make_string() << "Bye" ); // kills the application
g( (make_string() << "Hi again").c_str() ); // ok again std::string temporary is alive until g() returns
Now, try to explain what is so wrong with the second and not with the first or third lines one to the poor programmer whose application keeps dying. After all, she is just using a feature you are offering.
Another detail is that you could have a std::string member attribute and use it to force the lifespan of the std::string to be equivalent to that of the make_string object.
Anyway, I recomend everyone to read Modern C++ Design from Andrei Alexandrescu, and specially the chapter on smart pointers. The discussion of what features to implement is quite interesting. It changed my set of mind from 'implement as many features as you can' to a more conservative 'implement what is required, weight advantages and disadvantages of all other features'