1

I have the following C++ code that compiles successfully for gcc < v5.5

#include <sstream>

namespace util
{

   inline void operator << (std::stringstream& stream, const char* data)
   {
      data = data ? data : "(null)";
      std::operator<<(stream, data);
   }
}

class Record
{
public:

   template<typename T>
   Record& operator<<(const T& data)
   {
      using namespace util;
      m_message << data;
    //   std::cout << data << std::endl;
      return *this;
   }
   
private:
   std::stringstream m_message;
};

int main()
{   
    Record r;
    std::stringstream m;
    r << m; //Error after gcc5.5
    r << m.str(); //Ok in both
    return 0;
    
}

Looks like passing a stringstream to a stringstream is no longer valid. I'm not sure why.

Here's a link for comparison: https://godbolt.org/z/novWE3so8

Bilal Ahmed
  • 381
  • 2
  • 13
  • 1
    [Use correct header files 1st please!](https://stackoverflow.com/questions/31816095/why-should-i-not-include-bits-stdc-h?r=Saves_AllUserSaves) – πάντα ῥεῖ May 30 '23 at 10:52
  • 1
    AFAIK, that has never been part of any standard. It's possible that old g++ versions implemented it (the standard is only a minimum) and it was removed. – molbdnilo May 30 '23 at 11:41
  • Issue with whatever extensions is that those can be removed after whoever sponsored these decided to stop. What overload of << you expect that code to call? Tell the number from list: – Öö Tiib May 30 '23 at 11:43
  • @ÖöTiib That's not complete, though, see https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt2 – Aconcagua May 30 '23 at 12:11
  • @ÖöTiib, well, it doens't match any parameters there. Maybe this is no longer implemented by compilers as molbdhilo stated. – Bilal Ahmed May 30 '23 at 14:30
  • 1
    @BilalAhmed so you should add your own overload ... like Record& operator<<(const std::stringstream& m) ... if all you want is just to output m.str(), it will compile on other compilers and more modern gcc and that is usually benefit. – Öö Tiib May 30 '23 at 15:05

1 Answers1

1

This does not depend on the gcc version, but rather on the C++-Standard Version you're compiling for.

Your example works without problems with the latest gcc version when compiling for C++03: godbolt


The reason why this compiles at all is that pre-C++11 there was no bool datatype.
Checking if a stream is valid (e.g. if(stream) { /*...*/ }) therefore was mandated to just return some unspecified boolean-like type:

cppreference

operator /* unspecified-boolean-type */() const; // (until C++11)
explicit operator bool() const; // (since C++11)

Note that the conversion also was implicit pre C++11, since explicit conversion functions were added in C++11.

libstdc++ happens to define the conversion operator as follows: github

///@{
/**
 *  @brief  The quick-and-easy status check.
 *
 *  This allows you to write constructs such as
 *  <code>if (!a_stream) ...</code> and <code>while (a_stream) ...</code>
 */
#if __cplusplus >= 201103L
      explicit operator bool() const { return !this->fail(); }
#else
      operator void*() const { return this->fail() ? 0 : const_cast<basic_ios*>(this); }
#endif

Note that this allows an implicit (!) conversion from any stream to void*.

Which is unfortunate, given that std::basic_ostream provides an overload for outputting pointers:
cppreference

basic_ostream& operator<<( const void* value ); // (8)

So what actually happens in your example is that the stream is implicitly converted to a void* pointer, and then operator<<(const void*) is called to output that pointer.

Which is probably not what you wanted to happen:
godbolt

#include <iostream>
#include <sstream>

int main() {
    std::stringstream m;
    m << "foobar";

    std::stringstream ss;
    // equivalent to  ss << static_cast<void*>(&m);
    ss << m;

    std::cout << ss.str() << std::endl;
    // will print the address of m, e.g. 0x7ffcda4c2540
    // NOT foobar!
    return 0;
}

The correct way to append a stream

C++ std::istreams can be written to std::ostreams by just passing the underlying stream buffer instead of the whole stream, e.g.:
godbolt

#include <iostream>
#include <sstream>

int main() {
    std::stringstream m;
    m << "foobar";

    std::stringstream ss;
    ss << m.rdbuf(); // append everything from m to ss

    std::cout << ss.str() << std::endl;
    return 0;
}

So your example could be fixed like this:

// r << m;
r << m.rdbuf();
Turtlefight
  • 9,420
  • 2
  • 23
  • 40