6

I have a special type of ostringstream that I am trying to output text to as a temporary object but I'm having some trouble. To be clear, this is essentially what I want to do:

ostringstream() << "PARTY DOWN!" << endl;

Now before you say: "But Zack, that code is totally worthless! The object is destroyed at the end of the line, how would you even know if it did anything?", hear me out. I don't try to do this with plain ostringstreams, but rather a derived class in which the destructor actually provides a path for the data to exit the object. So in reality, it looks a lot more like this:

specialstringstream() << "PARTY DOWN!" << endl;

Where specialstringstream has a destructor which dumps the text elsewhere.

I won't go into too much detail as to why I do this. You'll have to trust me that it makes sense for what I need to do and it fits nicely into an existing, gigantic codebase.

Here's the problem: when I do this, everything compiles and runs, but I get a pointer address printed to my output instead of the "PARTY DOWN!" string. I determined that this is happening because the operator chosen by the compiler to perform the stream output is ostream& operator<< (const void* val), not ostream& operator<< (ostream& out, const char* s ).

I have a vague idea of why but I'm at a loss as to how to work around it. What can I do to get char*s to print into a temporary instance of a stringstream?

Here's a short version of the SpecialStringStream object that exhibits the behavior:

class SpecialStringStream : public ostringstream
{
  public:
    SpecialStringStream(ostream* regularStream)
    {
      regularStream_ = regularStream;
    }

    ~SpecialStringStream()
    {
      if (regularStream_ != NULL)
        (*regularStream_) << str(); 
    }

  private:
    ostream* regularStream_;
};

When I do something like: SpecialStringStream(someStreamPtr) << "PARTY DOWN!" << endl;, I get a pointer address like "00444D60" in my output instead of the message.

EDIT: Since I am too new of a user to answer my own question, here is what I've settled on thanks to all the responses.

I came up with the following solution, which works under Visual C++ 8 and all of the other compilers I needed it to. I created a template operator which basically strips a const SpecialStringStream of its constness, casts it as an ostream, and lets the ostream operators do their thing. Feel free to tear it to shreds in the comments and warn me of all the horrible potential bugs I've introduced!

template <class T>
std::ostream& operator<<(const SpecialStringStream &o, T msg)
{
    return static_cast<std::ostream&>(const_cast<SpecialStringStream&>(o)) << msg;
}
Zack Schilling
  • 125
  • 1
  • 9
  • 1
    I doubt that is the reason, because if it was, then `cout << "hi"` wouldn't work because it would "choose `ostream& operator<<(const void* val)` instead of `ostream& operator<<(const char* val)`". – Seth Carnegie Nov 04 '11 at 18:14
  • More code please.. specially your special string implemenation – parapura rajkumar Nov 04 '11 at 18:15
  • Can we please see the smallest complete implementation of SpecialStringStream that exhibits the behavior? (Also, this is not an unusual idiom -- you can easily imagine, e.g., `SomeLogger() << "error code: " << ec;` cheaply flushing the log buffer on temp object destruction.) – pilcrow Nov 04 '11 at 18:15
  • 1
    I'm sure that your implementation of specialstringstream() is wrong in some way... please paste code, it's interesting! – webbi Nov 04 '11 at 18:19
  • possible duplicate of [std::ostringstream printing the address of the c-string instead of its content](http://stackoverflow.com/questions/8287188/stdostringstream-printing-the-address-of-the-c-string-instead-of-its-content) – Matthieu M. Mar 09 '12 at 07:27

4 Answers4

4

The overload ostream& operator<< (ostream& out, const char*) is not viable because your temporary won't bind to the non-const reference ostream&. There isn't really much you can do about that (since you can't cast an rvalue to an lvalue) other than declaring a local variable and using that:

{
  specialstringstream ss;
  ss << "Hello world" << std::endl; // OK, can bind to lvalue
}

Possible solution: You could declare another overload that accepts an rvalue reference:

std::ostream & operator<<(specialstringstream && o, const char * s)
{
  return o << s; // note: *not* "std::move(o)"
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Brilliant again! I never would have thought of that. – Mooing Duck Nov 04 '11 at 18:24
  • @LokiAstari: I never claimed that. It's about rvalue vs lvalue. In your example, `std::cout` is an lvalue. – Kerrek SB Nov 04 '11 at 18:26
  • While this does work and I appreciate the help, I should have mentioned in my question that I was looking specifically for a workaround to get the code to work without making a change to every place the existing codebase performs output. Perhaps by adding an operator overload to the special class. – Zack Schilling Nov 04 '11 at 18:37
  • You could change your operator to `std::ostream & operator<<(const std::ostream & o, const char * s)` and use a `const_cast`. If you abuse that, it'll be UB, though. Alternatively, declare *another* overload `std::ostream & operator<<(std::ostream && o, const char * s)` to deal with rvlaues. – Kerrek SB Nov 04 '11 at 18:39
  • Alas, I need this code to compile in VC8. I can't seem to swing either solution in that environment. rvalue references aren't supported and it claims: "'const_cast' : Cannot convert from 'const ostream' to 'ostream'. Conversion requires a constructor or user-defined-conversion operator, which can't be used by const_cast or reinterpret_cast." I think what I'll have to do is make sure the functionality I've implemented here with my new class shuts off on older compilers. – Zack Schilling Nov 04 '11 at 19:22
  • @ZackSchilling: const cast should work in any flavour of C++. What exactly are you doing? It should be `const_cast(o) << str`. – Kerrek SB Nov 04 '11 at 19:24
  • That is precisely what I'm doing, to the letter. – Zack Schilling Nov 04 '11 at 19:25
  • To be clear, you're not crazy, the solution works in other compilers! – Zack Schilling Nov 04 '11 at 19:55
  • @ZackSchilling: I don't have VC8, so I can't speak on that matter, but the error message is weird (plus that's a really fundamental feature of C++, so it's pretty amazing that that'd be buggy) -- just to be sure, are you sure you have the ampersand in the cast? You can only cast the reference, not the type itself... – Kerrek SB Nov 04 '11 at 21:20
  • @KerrekSB yes, I'm sure. I was able to sidestep the bug or whatever it was. See my workaround in the edit to the question. – Zack Schilling Nov 04 '11 at 21:28
  • Oooh, I'm sorry, I confused this with my own code, where I did write `const_cast(o)` -- that alone should suffice; you won't need the additional cast to `std::ostream&`! – Kerrek SB Nov 04 '11 at 21:46
2

You don't want to implement stringstream. You want to implement a basic_streambuf that writes into your special string.

The streams themselves are responsible for formatting and similar functions; the streambufs are responsible for what ultimately becomes the sink for the data.

After all, all a stringstream is is a iostream, with a basic_stringbuf attached.

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
2

Maybe there is some better way but i figured an other solution:

#include <iostream>
#include <sstream>

class LogClass
{ 
    template <typename T>
    friend const LogClass& operator << (const LogClass& lc, const T& v);
public:
    LogClass()
        : str(new std::ostringstream())
        , refCount(new int(1))
    {}

    LogClass(const LogClass& other)
        : str(other.str)
    {
        ++(*refCount);
    }

    ~LogClass()
    {
        --(*refCount);
        if (!*refCount)
        {
            delete refCount;
            std::cout << str->str() << std::endl;
            delete str;
        }
    }
private:
    mutable std::ostringstream *str;
    int *refCount;

    LogClass& operator = (const LogClass&);
};

template <typename T>
const LogClass& operator << (const LogClass& lc, const T& v)
{
    (*lc.str) << v;
    return lc;
}

int main(int , char**)
{
    for (size_t i = 0; i < 10 ; ++i)
    {
        LogClass() << "PARTY DOWN! " << i;
    }
}

Running it with valgrind:

valgrind --tool=memcheck --leak-check=full ./LogClass

==16197== Memcheck, a memory error detector

==16197== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.

==16197== Using Valgrind-3.7.0.SVN and LibVEX; rerun with -h for copyright info

==16197== Command: ./LogClass

==16197==

PARTY DOWN! 0

PARTY DOWN! 1

PARTY DOWN! 2

PARTY DOWN! 3

PARTY DOWN! 4

PARTY DOWN! 5

PARTY DOWN! 6

PARTY DOWN! 7

PARTY DOWN! 8

PARTY DOWN! 9

==16197==

==16197== HEAP SUMMARY:

==16197== in use at exit: 0 bytes in 0 blocks

==16197== total heap usage: 40 allocs, 40 frees, 7,350 bytes allocated

==16197==

==16197== All heap blocks were freed -- no leaks are possible

==16197==

==16197== For counts of detected and suppressed errors, rerun with: -v

==16197== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 15 from 8)

Thats what i wanted, but it is not thread safe. Use shared_ptr from boost to make it so.

0

here is a workaround I'm using:

#define STRM2STR(x) (dynamic_cast<std::ostringstream &>(std::ostringstream() << std::dec << x).str())

insertion of std::dec will result in calling ostream::operator<< (ios_base& (*pf)(ios_base&)) which returns usable ostream &

Zrin
  • 919
  • 15
  • 25