0

I would like std::ostringstream to modify the string I pass it:

#include <string>
#include <iostream>
#include <sstream>

void My_Function(std::string& error_message)
{
  std::ostringstream error_stream(error_message);
  // For Nipun Talukdar:
  /* Perform some operations */
  if (/* operation failed */)
  {
      error_stream << "Failure at line: "
                   << __LINE__
                   << ", in source file: "
                   << __FILE__
                   << "\n";
  }
  return;
}

int main(void)
{
  std::string error_message;
  My_Function(error_message);
  std::cout << "Error is: \""
            << error_message
            << "\"\n";
  return 0;
}

With the above code, the output of error_message is empty.

This is because, according to cppreference.com, the constructor of std::basic_ostream that takes a std::stream takes a const reference to a std::string. This means that std::basic_ostringstream does not modify the string passed to it. The cited reference even says that std::ostringstream makes a copy of the string passed to it.

To get around this, I changed my function:

void My_Second_Function(std::string& error_message)
{
  std::ostringstream error_stream;
  error_stream << "Failure at line: "
               << __LINE__
               << "\n";
  error_message = error_stream.str();  // This is not efficient, making a copy!
  return;
}

Is there a more efficient method to perform formatted output to a string, such as a direct write (i.e. without have to copy from the stream)?

I'm using Visual Studio 2010, which does not support C++11. Due to shop considerations, the justification of upgrading to 2013 did not pass. So I can't use C++11 or C++14 features.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • Use a stream buffer and set put pointers accordingly. – David G Mar 12 '15 at 18:11
  • 2
    Not related to the question, but won't it print the same line number always? – Nipun Talukdar Mar 12 '15 at 18:12
  • @NipunTalukdar: The `__LINE__` macro returns the line number in the source code. The line number may change if code is inserted or removed before location of the `__LINE__` macro. – Thomas Matthews Mar 12 '15 at 18:14
  • 1
    @0x499602D2: Please provide an answer containing an example of your comment. Thanks. – Thomas Matthews Mar 12 '15 at 18:15
  • You might want to look at using [RVO](http://en.wikipedia.org/wiki/Return_value_optimization) – NathanOliver Mar 12 '15 at 18:16
  • What's wrong with `std::string::append`? – Emil Laine Mar 12 '15 at 18:18
  • 1
    @ThomasMatthews Yes. But it will always print the same line number in My_Function. So, each call to My_Function will output the same line number. – Nipun Talukdar Mar 12 '15 at 18:30
  • @zenith: The [std::string::append](http://en.cppreference.com/w/cpp/string/basic_string/append) method does not format internal integer representations, nor variables. – Thomas Matthews Mar 12 '15 at 18:53
  • @NipunTalukdar: I edited my post, adding clarity to the function. By the way, if the __LINE__ macro is not used, how does one print the line number of failure and *keep it accurate when code is removed or added before the function*? – Thomas Matthews Mar 12 '15 at 18:59
  • @ThomasMatthews: Usually like this `#define My_Function(string) My_Function_Implementation(__FILE__,__LINE__,string)`, which puts the `__LINE__` macro at the location of the caller instead of at the location of `My_Function`. Check out the `assert` macro, which does the same thing. – Mooing Duck Mar 12 '15 at 19:24

1 Answers1

1

Use a stream buffer and set the put pointers to the string's internal data:

struct nocopy : std::streambuf
{
    nocopy(std::string& str)
    { this->setp(&str[0], &str[0] + str.size()); }
};

struct nocopy_stream : virtual private nocopy, std::ostream
{
    nocopy_stream(std::string& str)
        : nocopy(str)
        , std::ostream(this)
    { }
};

void My_Function(std::string& error_message)
{
  nocopy_stream error_stream(error_message);
  error_stream << "Failure at line: "
               << __LINE__
               << "\n";
}

int main(void)
{
  std::string error_message;
  error_message.resize(1000);

  My_Function(error_message);
  std::cout << "Error is: \""
            << error_message
            << "\"\n";
}

For this example error_message has to be set to a large enough size as we do not override overflow() and the base class version does nothing. You can override it to do the correct resizing however.

David G
  • 94,763
  • 41
  • 167
  • 253
  • There's a lot more to it than that. Your streambuf doesn't change the size of string it's given; if you output more, then there will just be an output error, and if you output less, there will be a lot of junk at the end. (Of course, the whole question is specious. Any way you do it, there's bound to be some copying; on the other hand, the cost of copying should be negligible, compared to everything else.) – James Kanze Mar 12 '15 at 18:33
  • @JamesKanze Of course it's more work than this. Maybe you can give a sufficient answer? – David G Mar 12 '15 at 18:34
  • @JamesKanze, @0x499602D2: So, why does `ostringstream` make a copy of the string you pass it? – Thomas Matthews Mar 12 '15 at 19:02
  • @ThomasMatthews: That's actually a really good question. R.Sahu suggests it's for appending, but I think it's mere oversight. – Mooing Duck Mar 12 '15 at 19:25
  • @ThomasMatthews Because that's what it is defined to do. The underlying reason is that `std::stringbuf` doesn't know whether it is to be used for input or output; if for input, it reads from the string you gave it. A more logical solution might have been to not have an `std::stringstream` class at all, and to have two different string buffers, one for input and one for output. The philosophy of the IO streams, however, has always been to support bidirectional streams, Even if we now know that they are of limited use. – James Kanze Mar 13 '15 at 12:57