behaviour change in operator<<

  • Thread starter Thread starter Carlo Capelli
  • Start date Start date
C

Carlo Capelli

I found a change in the following code, that behaved
correctly in VC++ 6.

#include <strstream>
using namespace std;
void main()
{
char x[100];
ostrstream(x, 100) << "pippo" << "pluto" << ends;
// here x contains "004400C8pluto"
}

Clearly,

basic_ostream::operator<<(const void*)
have been called, instead of
basic_ostream::operator<<(const char*).

Some change in scoping rule (due to the introduction of
Koenig Lookup) justify this change?

Many Thanks for your attention.
 
Carlo said:
I found a change in the following code, that behaved
correctly in VC++ 6.

#include <strstream>
using namespace std;
void main()
{
char x[100];
ostrstream(x, 100) << "pippo" << "pluto" << ends;
// here x contains "004400C8pluto"
}

Clearly,

basic_ostream::operator<<(const void*)
have been called, instead of
basic_ostream::operator<<(const char*).

Some change in scoping rule (due to the introduction of
Koenig Lookup) justify this change?

Many Thanks for your attention.

It's not a bug. There is a member output operator for void*, but the char*,
signed char*, and unsigned char* output operators are non-members whose
first parameter is a non-const ostream&. The compiler is choosing the member
operator, because you can call member functions through temporary objects,
e.g. X().f(), or here, ostream().operator<<("pippo"), but you can't bind
temporaries to non-const references, which eliminates the non-member
operators. So:

ostrstream(x, 100) << "pippo" << "pluto" << ends;

calls the operator<<(const void*) member and stores a pointer value[1],
while:

ostrstream os(x, 100);
os << "pippo" << "pluto" << ends;

calls the non-member operator<<(ostream&, const char*) and stores the
strings.

The craziest thing is that this is apparently the way it's supposed to work.
The non-member operators<< are rejected in the first case as non-viable,
because selecting them would require binding a temporary to a non-const
reference, leaving the member operator<<(const void*) to handle the output
of the string literal. FWIW, I'm not convinced it makes sense to eliminate
functions from overload resolution based on the reference binding issue, but
that's what the standard says.

[1] The string "pluto" is stored after the address of "pippo", because
operator<< returns ostream&, and there's no problem binding it to the first
parameter of operator<<(ostream&, const char*).
 
From: "Doug Harrison said:
Subject: Re: behaviour change in operator<<
Date: Wed, 13 Aug 2003 13:23:36 -0500
Message-ID: <[email protected]>
References: <[email protected]>

Carlo said:
I found a change in the following code, that behaved
correctly in VC++ 6.

#include <strstream>
using namespace std;
void main()
{
char x[100];
ostrstream(x, 100) << "pippo" << "pluto" << ends;
// here x contains "004400C8pluto"
}

Clearly,

basic_ostream::operator<<(const void*)
have been called, instead of
basic_ostream::operator<<(const char*).

Some change in scoping rule (due to the introduction of
Koenig Lookup) justify this change?

Many Thanks for your attention.

It's not a bug. There is a member output operator for void*, but the char*,
signed char*, and unsigned char* output operators are non-members whose
first parameter is a non-const ostream&. The compiler is choosing the member
operator, because you can call member functions through temporary objects,
e.g. X().f(), or here, ostream().operator<<("pippo"), but you can't bind
temporaries to non-const references, which eliminates the non-member
operators. So:

ostrstream(x, 100) << "pippo" << "pluto" << ends;

calls the operator<<(const void*) member and stores a pointer value[1],
while:

ostrstream os(x, 100);
os << "pippo" << "pluto" << ends;

calls the non-member operator<<(ostream&, const char*) and stores the
strings.

The craziest thing is that this is apparently the way it's supposed to work.
The non-member operators<< are rejected in the first case as non-viable,
because selecting them would require binding a temporary to a non-const
reference, leaving the member operator<<(const void*) to handle the output
of the string literal. FWIW, I'm not convinced it makes sense to eliminate
functions from overload resolution based on the reference binding issue, but
that's what the standard says.

[1] The string "pluto" is stored after the address of "pippo", because
operator<< returns ostream&, and there's no problem binding it to the first
parameter of operator<<(ostream&, const char*).

Actually there is a bug here :-( You can only call const member functions
on a temporary
object of a class type - and as all the ostream::operator<< functions are
non-const and all the
global operator<< require a ostream& as the first parameter the compiler
should not be able to
call any of the available functions. This code should not compile: I quick
check with the Comeau
indicates that the EDG compiler does indeed reject this code.

I've added the issue to our bug-database.
 
From: "Doug Harrison said:
Subject: Re: behaviour change in operator<<
Date: Wed, 13 Aug 2003 14:52:21 -0500
Message-ID: <[email protected]>
References: <[email protected]>
Perhaps strangely, that's incorrect. Things like this are perfectly legal:

struct X
{
X& operator=(const X&);
};

void f()
{
X() = X();
}

The non-const reference binding issue is solely responsible for this
weirdness:

void g(X&);

void h()
{
g(X()); // 1. Bad!
g(X() = X()); // 2. Fine!
}

Now add:

void g(const X&);

to the above and try again. You'll see that (1) now compiles, but note that
it calls the new overload which takes a const X&. On the other hand, (2)
calls the original g which takes plain X&. This is what I find hard to
justify for class types. It seems to me that a non-const X should bind to
X&, whether it's an rvalue or not. IIRC, when I experimented with this in
VC6 a couple of years ago, that's the rule VC6 implemented, while still
forbidding the binding for built-in types. I think that makes a lot of
sense, but it isn't standard. (NB: When using VC, compile the fragments with
-Za to get standard conformant behavior, which is what I've assumed above.)

Sigh: you are correct Doug: somehow in reducing the test case to a more
simple example I
changed it enough that the code did not compile. I admit the behavior does
appear strange
but I've convinced myself that it is allowed by the Standard. So you are
correct we don't
have a bug.
I think you can unbug it. :)

It's been debugged :-)
 
Back
Top