Reputation: 302748
Given some type that is streamable:
struct X {
int i;
friend std::ostream& operator<<(std::ostream& os, X const& x) {
return os << "X(" << x.i << ')';
}
};
I want to append this onto a std::string
. I can implement this as:
void append(std::string& s, X const& x) {
std::ostringstream os;
os << x;
s.append(os.str());
}
But this seems lame since I'm writing data into one stream just to then allocate a new string just for the purposes of appending it onto a different one. Is there a more direct route?
Upvotes: 23
Views: 4868
Reputation: 9210
I feel like someone should have mentioned the (obvious?) one-liner:
void append(std::string& s, X const& x) {
s.append((std::ostringstream() << x).str());
}
Upvotes: 0
Reputation: 176
You could write a std::string casting operator:
struct X {
int i;
friend std::ostream& operator<<(std::ostream& os, X const& x) {
os << "X(" << x.i << ')';
return os;
}
operator std::string() {
return std::string("X(") + std::to_string(x.i) + ")";
}
};
Then, you could simply append it to a std::string:
X myX;
myX.i = 2;
std::string s("The value is ");
s.append(myX); //myX is cast into the string "X(2)"
Upvotes: 2
Reputation: 42828
In this specific case I'd just follow the KISS principle:
struct X {
int i;
std::string toString() const {
return "X(" + std::to_string(i) + ")";
}
};
Usage:
string += x.toString();
std::cout << x.toString();
An operator<<(std::ostream&, …)
isn't really suitable for generic string conversion, so if that's what you're after then a toString
type of method / free function is much better. And if you want std::cout << x
you can trivially implement operator<<
to just call toString
.
Upvotes: 1
Reputation: 76297
This can be solved by a new type of streambuf
(see Standard C++ IOStreams and Locales: Advanced Programmer's Guide and Reference).
Here is a sketch of how it can look:
#include <streambuf>
class existing_string_buf : public std::streambuf
{
public:
// Store a pointer to to_append.
explicit existing_string_buf(std::string &to_append);
virtual int_type overflow (int_type c) {
// Push here to the string to_append.
}
};
Once you flesh out the details here, you could use it as follows:
#include <iostream>
std::string s;
// Create a streambuf of the string s
existing_string_buf b(s);
// Create an ostream with the streambuf
std::ostream o(&b);
Now you just write to o
, and the result should appear as appended to s
.
// This will append to s
o << 22;
Edit
As @rustyx correctly notes, overriding xsputn
is required for improving performance.
Full Example
The following prints 22
:
#include <streambuf>
#include <string>
#include <ostream>
#include <iostream>
class existing_string_buf : public std::streambuf
{
public:
// Somehow store a pointer to to_append.
explicit existing_string_buf(std::string &to_append) :
m_to_append(&to_append){}
virtual int_type overflow (int_type c) {
if (c != EOF) {
m_to_append->push_back(c);
}
return c;
}
virtual std::streamsize xsputn (const char* s, std::streamsize n) {
m_to_append->insert(m_to_append->end(), s, s + n);
return n;
}
private:
std::string *m_to_append;
};
int main()
{
std::string s;
existing_string_buf b(s);
std::ostream o(&b);
o << 22;
std::cout << s << std::endl;
}
Upvotes: 16
Reputation: 1871
Since the original string is likely only large enough for the existing allocation, the best you can hope for is to format everything you want to append once in the stream, then append the result as you have in your example.
If you plan on performing these appends often, I would argue std::string is the wrong type for the problem at hand. I would recommend using an std::ostringtream directly instead of a std::string, and only convert to a string when you need the final result.
Upvotes: -1