Barry
Barry

Reputation: 302748

Stream object directly into a std::string

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

Answers (5)

vitiral
vitiral

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

HeywoodFloyd
HeywoodFloyd

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

Emil Laine
Emil Laine

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

Ami Tavory
Ami Tavory

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

nate
nate

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

Related Questions