Calling constructors and temporary objects

D

David Given

(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?
 
K

Kai-Uwe Bux

David said:
(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
 
D

David Given

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?
 
K

Kai-Uwe Bux

David said:
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
 
D

David Given

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!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top