views:

157

answers:

5

Consider the following minimal example:

#include <iostream>

using namespace std;

class myostream : public ostream {
    public:
        myostream(ostream const &other) :
            ostream(other.rdbuf())
        { }
};

int main() {
    cout << "hello world" << endl;

    myostream s(cout);
    s << "hello world" << endl;

    myostream(cout) << "hello world" << endl;
}

The output, both on g++ and on Visual C++, is

hello world
hello world
0x4012a4

The version that writes to a temporary object, myostream(cout), appears to prefer the member operator ostream::operator<<(void *), instead of the free operator operator<<(ostream &, char *). It seems to make a difference whether or not the object has a name.

Why does this happen? And how do I prevent this behaviour?

Edit: Why it happens is now clear from various answers. As to how to prevent this, the following seems appealing:

class myostream : public ostream {
    public:
        // ...
        myostream &operator<<(char const *str) {
            std::operator<<(*this, str);
            return *this;
        }
};

However, this results in all kinds of ambiguities.

+3  A: 

I just realized part of the answer. The temporary is not an lvalue, so it cannot be used as an argument of type ostream &.

The question "how can I make this work" remains...

Thomas
The simplest: provide member functions that make the work for you.
David Rodríguez - dribeas
+7  A: 

If an object doesn't have a name (i.e. it is a temporary), it cannot be bound to a non-const reference. Specifically, it can't be bound to the first parameter of:

operator<<(ostream &, char *)
anon
.. till C++0x comes along, and empowers us with r-value references.
dirkgently
A: 

Well, I don't know the C++ spec that causes this, but it is easy to suss out why it happens.

A temporary lives on the stack, usually to be passed to another function or to have a single operation called on it. So, if you call the free operator on it:

operator<<(myostream(cout))

It is destroyed at the end of this operation and the second "<<" operator to append the endl would reference an invalid object. The return value from the free "<<" operator would be a reference to a destructed temporary object. The C++ spec probably defines rules about free operators to prevent this scenario from frustrating and confusing C++ programmers.

Now, in the case of a "<<(void*)" member operator on the temporary, the return value is the object itself, which is still on the stack and not destroyed, so the compiler knows not to destruct it but to pass it to the next member operator, the one that takes the endl. Operator chaining on temporaries is a useful feature for succinct C++ code, so I'm sure the C++ spec designers considered it and implemented the compiler to support it intentionally.

edit

Some have said that it is to do with a non-const reference. This code compiles:

#include <iostream>
using namespace std;
class myostream : public ostream { 
    public: 
        myostream(ostream const &other) : 
            ostream(other.rdbuf()) 
        { } 
            ~myostream() { cout << " destructing "; }
    }; 
int _tmain(int argc, _TCHAR* argv[])
{
    basic_ostream<char>& result = std::operator << (myostream(cout), "This works");
    std::operator << (result, "illegal");
         return 0;
}

And it returns

  This works destructing illegal
David Gladfelter
Sorry - your understanding of the lifetimes of temporaries is wrong.
anon
Please feel free to enlighten me. AFAIK, the description here is consistent with my explanation: http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr382.htm. If you use a static method on a temporary, the temporary must be destroyed when the call stack for that operation is cleaned up. If it is used as the "this" for a member operation, then it can continue to live after the operation has completed since it is at the bottom of the call stack, thus chained member operators will work but static methods won't.
David Gladfelter
The link you posted is to a well-known poor source of C++ info (and exhibits weird behaviour, flipping me to another page). The C++ Standard is the reference here, and it says (effectively) that the temporary must hang around until the end of the complete expression it is part of, which in this case is the complete chain of << operators. The real reason the code doesn't work as expected is given in my answer.
anon
This is one of the things that I admit not to know of, but to me `a << b << c;` is syntactic sugar for `operator<<( operator<<( a, b ), c );`, so the temporary was created as part of the whole expression and as the second sentence in the article you link to, should be destroyed at the `;`, am I right?
David Rodríguez - dribeas
It;s not to do with non-const references, it's to do with temporaries. A named non-const reference can be bound to a non-const reference parameter, an unnamed temporary cannot.
anon
@David G - your code compiles because VC++ allows binding a non-const ref. to an r-value as a non-portable extension (but you should get a warning)
Manuel
@Manuel, thanks for the tip, but no warning was generated. If that's so, why the the VC++ compiler give the same results as the question-asker? For an entirely unrelated reason?
David Gladfelter
@David Gladfelter: that code (after correcting the main signature to make it standard) will not compile in comeau, g++ or intel compilers. The error messages differ a little, but all of them say that they could not find an appropriate overload. Also note that from the output of your program it is clear that you have bound a reference to a temporary, and used it after the temporary was destroyed, invoking undefined behavior.
David Rodríguez - dribeas
@David G - you have to set "warning level = 4" to see the warning
Manuel
@David Rodriguez, yeah, that's why I used the word "illegal". Thanks for the tip about the VC++ compiler. Funny thing is that MSDN says that that temporaries can't be used for non-const references in VS 2005 (and greater, presumably), but they obviously still work in VS 2008. I do get a warning if I turn on error level 4.
David Gladfelter
+3  A: 

rvalues can't be bound to non-const reference. So in your example the temporary of type ostream can't be the first argument of free operator<<(std::ostream&, char const*) and what is used is the member operator<<(void*).

If you need it, you can add a call such as

myostream(cout).flush() << "foo";

which will transform the rvalue into a reference.

Note that in C++0X, the introduction of rvalue reference will allow to provide overload of operator<< taking rvalue references as parameter, solving the root cause of the issue.

AProgrammer
Did you mean "rvalues can't be bound..."? and "... transform the rvalue into an lvalue..."?
Éric Malenfant
Right, I've fixed my response. Thanks.
AProgrammer
A: 

Since none of the answers so far seem to give a clean solution, I will settle for the dirty solution:

myostream operator<<(myostream stream, char const *str) {
    std::operator<<(stream, str);
    return stream;
}

This is only possible because myostream has a copy constructor. (Internally, it is backed by a ref-counted std::stringbuf.)

Thomas