22

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?

Barry
  • 286,269
  • 29
  • 621
  • 977
  • I'd look towards http://www.boost.org/doc/libs/1_61_0/doc/html/interprocess/streams.html – bobah Aug 03 '16 at 20:27
  • Maybe implement `std::string X::repr()` or similar, and call that in both `append` and `operator<<`? Not ideal, but you avoid the intermediate `stringstream`. – Praetorian Aug 03 '16 at 20:28
  • What about simply `s.append(std::string::to_string(x));`? Did I miss something essential? – πάντα ῥεῖ Aug 03 '16 at 20:30
  • This [question](http://stackoverflow.com/questions/1494182/setting-the-internal-buffer-used-by-a-standard-stream-pubsetbuf) may have some useful information – GWW Aug 03 '16 at 20:31
  • 2
    Maybe overloading `std::string& operator+=(std::string& s, X const& x)`? This definitely would be more direct ;) – W.F. Aug 03 '16 at 21:28

4 Answers4

15

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;
}   
Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
  • 6
    There are times when I feel like I know C++. This is not one of those times. – Barry Aug 03 '16 at 21:29
  • Is there any reason to store a pointer and not a reference? Does the buffer have to be copyable? – NathanOliver Aug 03 '16 at 22:57
  • @NathanOliver That's a very good point (thanks!), but it unfortunately exceeds my knowledge (the pointer was indeed chosen just so not to risk it). On the one hand, `streambuf` seems to be required to have a copy ctor; on the other hand, the pointer argument of the `ostream` taking it seems to indicate it's not copied. Bottom line - not sure. – Ami Tavory Aug 04 '16 at 07:13
2

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)"
HeywoodFloyd
  • 166
  • 1
  • 12
1

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.

Emil Laine
  • 41,598
  • 9
  • 101
  • 157
-1

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.

nate
  • 1,771
  • 12
  • 17