Velocity Reviews

Velocity Reviews (http://www.velocityreviews.com/forums/index.php)
-   C++ (http://www.velocityreviews.com/forums/f39-c.html)
-   -   Calling constructors and temporary objects (http://www.velocityreviews.com/forums/t620124-calling-constructors-and-temporary-objects.html)

David Given 06-13-2008 10:03 PM

Calling constructors and temporary objects
 
(No, I'm *not* trying to call a constructor from another constructor!)

I have this class:

class P : public std::stringstream
{
public:
~P() { std::cout << str(); }
};

That is, it behaves like a stringstream, but on destruct it prints
itself. This allows me to do this:

{
P p;
p << "Hello, world!";
}

....and I get 'Hello world!' on stdout.

However, if instead I do this:

P() << "Hello world!";

....I get '0x12345678' instead (that is, some random hex number).

Obviously what's happening is that it's not finding the const char*
iostream method on P, and is falling back to the void* one; but I don't
understand why. The temporary P instance is getting created and
destructed at the appropriate points, but it's behaving as if it's lost
some of its stringstream methods.

I use this idiom extensively for various printers and loggers and so on;
they all work fine. This is the first time I've tried it with a subclass
of stringstream, though; all the others have delegated to an embedded
instance of stringstream rather than subclassing it. I thought I'd save
myself some typing this way. But why doesn't it work? More importantly,
why is it not working in this really bizarre fashion?

--
┌─── dg*cowlark.com ───── http://www.cowlark.com ─────
│ "I have always wished for my computer to be as easy to use as my
│ telephone; my wish has come true because I can no longer figure out
│ how to use my telephone." --- Bjarne Stroustrup

Kai-Uwe Bux 06-13-2008 10:24 PM

Re: Calling constructors and temporary objects
 
David Given wrote:

> (No, I'm *not* trying to call a constructor from another constructor!)
>
> I have this class:
>
> class P : public std::stringstream
> {
> public:
> ~P() { std::cout << str(); }
> };
>
> That is, it behaves like a stringstream, but on destruct it prints
> itself. This allows me to do this:
>
> {
> P p;
> p << "Hello, world!";
> }
>
> ...and I get 'Hello world!' on stdout.
>
> However, if instead I do this:
>
> P() << "Hello world!";
>
> ...I get '0x12345678' instead (that is, some random hex number).
>
> Obviously what's happening is that it's not finding the const char*
> iostream method on P, and is falling back to the void* one; but I don't
> understand why.


The reason is that one is a member function (the one that is found) and the
other is a free function that is rejected as a match because the compiler
is not allowed to bind the non-const stream reference to the temporary P().


> The temporary P instance is getting created and
> destructed at the appropriate points, but it's behaving as if it's lost
> some of its stringstream methods.
>
> I use this idiom extensively for various printers and loggers and so on;
> they all work fine. This is the first time I've tried it with a subclass
> of stringstream, though; all the others have delegated to an embedded
> instance of stringstream rather than subclassing it. I thought I'd save
> myself some typing this way. But why doesn't it work? More importantly,
> why is it not working in this really bizarre fashion?


The culprit is clause [8.5.3/5].

Note: your case is similar to this

std::vector<int>().swap( my_vector ); // legal
swap( std::vector<int>(), my_vector ); // not legal
my_vector.swap( std::vector<int>() ); // not legal

The difference is that the compiler finds a match using a conversion to
void* after all illegal readings have been ruled out. It doesn't really
make all that much sense, but this is the state of affairs in C++.


Best

Kai-Uwe Bux

David Given 06-14-2008 12:08 AM

Re: Calling constructors and temporary objects
 
Kai-Uwe Bux wrote:
[...]
> The reason is that one is a member function (the one that is found) and the
> other is a free function that is rejected as a match because the compiler
> is not allowed to bind the non-const stream reference to the temporary P().

[...]
> The culprit is clause [8.5.3/5].


If I've understood this correctly, this is all due to not being able to
initialise a non-const reference from a temporary, right?

Here's a cleaner test case that fails in the same way (on gcc. MSVC is
quite happy with it, annoyingly enough.)

struct SUPER { };
struct SUB : SUPER { };
SUPER& operator<<(SUPER& s, int i) {}

{ SUB() << 1; }

Given that the temporary goes out of scope at the end of the statement,
I don't see why this restriction is in place --- it should be possible
for the compiler to implicitly bind the reference, pass it to
operator<<(), have operator<<() work on it and then return it, and then
have the temporary get destructed. It's precisely the same semantic as
if I'd used a non-temporary.

So, why isn't this possible, and are there any workarounds? Could I have
SUB automatically cast itself to an object of the right type, for example?

--
┌─── dg*cowlark.com ───── http://www.cowlark.com ─────
│ "I have always wished for my computer to be as easy to use as my
│ telephone; my wish has come true because I can no longer figure out
│ how to use my telephone." --- Bjarne Stroustrup

Kai-Uwe Bux 06-14-2008 12:22 AM

Re: Calling constructors and temporary objects
 
David Given wrote:

> Kai-Uwe Bux wrote:
> [...]
>> The reason is that one is a member function (the one that is found) and
>> the other is a free function that is rejected as a match because the
>> compiler is not allowed to bind the non-const stream reference to the
>> temporary P().

> [...]
>> The culprit is clause [8.5.3/5].

>
> If I've understood this correctly, this is all due to not being able to
> initialise a non-const reference from a temporary, right?


Yes.

> Here's a cleaner test case that fails in the same way (on gcc. MSVC is
> quite happy with it, annoyingly enough.)
>
> struct SUPER { };
> struct SUB : SUPER { };
> SUPER& operator<<(SUPER& s, int i) {}
>
> { SUB() << 1; }
>
> Given that the temporary goes out of scope at the end of the statement,
> I don't see why this restriction is in place --- it should be possible
> for the compiler to implicitly bind the reference, pass it to
> operator<<(), have operator<<() work on it and then return it, and then
> have the temporary get destructed. It's precisely the same semantic as
> if I'd used a non-temporary.
>
> So, why isn't this possible, and are there any workarounds? Could I have
> SUB automatically cast itself to an object of the right type, for example?


You can make sure that there is always a matching member function:

struct P : public std::stringstream {

template < typename T >
P & operator<< ( T const & t ) {
static_cast< std::stringstream & >( *this ) << t;
return ( *this );
}

~P() { std::cout << str(); }
};


Best

Kai-Uwe Bux

David Given 06-14-2008 12:54 AM

Re: Calling constructors and temporary objects
 
Kai-Uwe Bux wrote:
[...]
> You can make sure that there is always a matching member function:
>
> struct P : public std::stringstream {
>
> template < typename T >
> P & operator<< ( T const & t ) {
> static_cast< std::stringstream & >( *this ) << t;
> return ( *this );
> }


Hmm. I hadn't thought of using templates in this way. That's very
interesting --- I may be able to apply it to my old delegation approach,
too.

Okay, I'll go and do more investigation. Thanks for the assistance!

--
┌─── dg*cowlark.com ───── http://www.cowlark.com ─────
│ "I have always wished for my computer to be as easy to use as my
│ telephone; my wish has come true because I can no longer figure out
│ how to use my telephone." --- Bjarne Stroustrup


All times are GMT. The time now is 01:18 PM.

Powered by vBulletin®. Copyright ©2000 - 2014, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.