Bad use of stringstream temporary?

K

K. Frank

Hello Group!

The basic question is whether the following line of code
is legal and good (according to the current standard):

std::string s1 =
dynamic_cast<std::stringstream&>(std::stringstream() << "abc").str();

The problem is that "abc" gets rendered as a hex pointer
value, rather than as "abc".

This is a follow-up on my posting on the MinGW Users List.
You can find that thread, with more detail and variations
on the theme, here:

http://thread.gmane.org/gmane.comp.gnu.mingw.user/35926

The problem is that we (or at least I) haven't been able
to identify any specific error in the code, yet three
different compilers print out the "erroneous" pointer-value
result (several versions of mingw g++, Comeau 4.3.10.1, and
msvc 9).

One speculation is that the code is illegal under the
current standard, but legal under c++0x. It is the
case that mingw g++ 4.5 and 4.6 print out the "correct"
result of "abc" when using the "-std=c++0x" option.
(mingw g++ 4.4 still prints out the "incorrect" pointer
value with "-std=c++0x".)

Here is a short test program and its output:


<< stringstream_test.cpp >>

#include <iostream>
#include <sstream>
#include <typeinfo>
int main (int argc, char *argv[]) {

std::string s1 =
dynamic_cast<std::stringstream&>(std::stringstream() << "abc").str();
std::cout << "expecting abc: " << s1 << std::endl; // prints out
"abc" as a pointer

std::stringstream sstr;
std::string s2 = dynamic_cast<std::stringstream&>(sstr <<
"xyz").str();
std::cout << "expecting xyz: " << s2 << std::endl;

}


C:\>g++ -o stringstream_test stringstream_test.cpp

C:\>stringstream_test
expecting abc: 0x477024
expecting xyz: xyz


That is, the two very similar test cases give different
results. The version with the unnamed temporary
stringstream prints out the "incorrect" pointer value,
while the version with the named local variable works
as expected.

(Again, more variations on this sample program that fail
to compile or give unexpected results can be found in the
thread on the MinGW Users List linked to above.)

So, is this code in error (and, if so, what's the specific
problem)? Or have we stumbled across a cluster of similar
bugs in a number of different compilers / libraries?

Thanks for your insight.


K. Frank
 
V

Victor Bazarov

The basic question is whether the following line of code
is legal and good (according to the current standard):

std::string s1 =
dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();

The problem is that "abc" gets rendered as a hex pointer
value, rather than as "abc".

This is a follow-up on my posting on the MinGW Users List.
You can find that thread, with more detail and variations
on the theme, here:

http://thread.gmane.org/gmane.comp.gnu.mingw.user/35926

The problem is that we (or at least I) haven't been able
to identify any specific error in the code, yet three
different compilers print out the "erroneous" pointer-value
result (several versions of mingw g++, Comeau 4.3.10.1, and
msvc 9).

One speculation is that the code is illegal under the
current standard, but legal under c++0x. It is the
case that mingw g++ 4.5 and 4.6 print out the "correct"
result of "abc" when using the "-std=c++0x" option.
(mingw g++ 4.4 still prints out the "incorrect" pointer
value with "-std=c++0x".)

Here is a short test program and its output:


<< stringstream_test.cpp>>

#include<iostream>
#include<sstream>
#include<typeinfo>
int main (int argc, char *argv[]) {

std::string s1 =
dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();
std::cout<< "expecting abc: "<< s1<< std::endl; // prints out
"abc" as a pointer

std::stringstream sstr;
std::string s2 = dynamic_cast<std::stringstream&>(sstr<<
"xyz").str();
std::cout<< "expecting xyz: "<< s2<< std::endl;

}


C:\>g++ -o stringstream_test stringstream_test.cpp

C:\>stringstream_test
expecting abc: 0x477024
expecting xyz: xyz


That is, the two very similar test cases give different
results. The version with the unnamed temporary
stringstream prints out the "incorrect" pointer value,
while the version with the named local variable works
as expected.

(Again, more variations on this sample program that fail
to compile or give unexpected results can be found in the
thread on the MinGW Users List linked to above.)

So, is this code in error (and, if so, what's the specific
problem)? Or have we stumbled across a cluster of similar
bugs in a number of different compilers / libraries?

The only function possible is the member function operator<< with the
argument that is void*. The non-member operator<<(stream&, const char*)
cannot be used because there is no binding of a temporary to a reference
to non-const.

The behavior is as expected.

V
 
J

Jeff Flinn

Victor said:
The basic question is whether the following line of code
is legal and good (according to the current standard):

std::string s1 =
dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();

The problem is that "abc" gets rendered as a hex pointer
value, rather than as "abc".

This is a follow-up on my posting on the MinGW Users List.
You can find that thread, with more detail and variations
on the theme, here:

http://thread.gmane.org/gmane.comp.gnu.mingw.user/35926

The problem is that we (or at least I) haven't been able
to identify any specific error in the code, yet three
different compilers print out the "erroneous" pointer-value
result (several versions of mingw g++, Comeau 4.3.10.1, and
msvc 9).

One speculation is that the code is illegal under the
current standard, but legal under c++0x. It is the
case that mingw g++ 4.5 and 4.6 print out the "correct"
result of "abc" when using the "-std=c++0x" option.
(mingw g++ 4.4 still prints out the "incorrect" pointer
value with "-std=c++0x".)

Here is a short test program and its output:


<< stringstream_test.cpp>>

#include<iostream>
#include<sstream>
#include<typeinfo>
int main (int argc, char *argv[]) {

std::string s1 =
dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();
std::cout<< "expecting abc: "<< s1<< std::endl; // prints out
"abc" as a pointer

std::stringstream sstr;
std::string s2 = dynamic_cast<std::stringstream&>(sstr<<
"xyz").str();
std::cout<< "expecting xyz: "<< s2<< std::endl;

}


C:\>g++ -o stringstream_test stringstream_test.cpp

C:\>stringstream_test
expecting abc: 0x477024
expecting xyz: xyz


That is, the two very similar test cases give different
results. The version with the unnamed temporary
stringstream prints out the "incorrect" pointer value,
while the version with the named local variable works
as expected.

(Again, more variations on this sample program that fail
to compile or give unexpected results can be found in the
thread on the MinGW Users List linked to above.)

So, is this code in error (and, if so, what's the specific
problem)? Or have we stumbled across a cluster of similar
bugs in a number of different compilers / libraries?

The only function possible is the member function operator<< with the
argument that is void*. The non-member operator<<(stream&, const char*)
cannot be used because there is no binding of a temporary to a reference
to non-const.

The behavior is as expected.

IIRC,

std::string s = (std::eek:stringstream() << std::flush << "abc").string();

behaves as the OP expected, at least on MSVC8 and gcc4.0.1 under XCode
3.1.2.

Jeff
 
V

Victor Bazarov

Victor said:
The basic question is whether the following line of code
is legal and good (according to the current standard):

std::string s1 =
dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();

The problem is that "abc" gets rendered as a hex pointer
value, rather than as "abc".

This is a follow-up on my posting on the MinGW Users List.
You can find that thread, with more detail and variations
on the theme, here:

http://thread.gmane.org/gmane.comp.gnu.mingw.user/35926

The problem is that we (or at least I) haven't been able
to identify any specific error in the code, yet three
different compilers print out the "erroneous" pointer-value
result (several versions of mingw g++, Comeau 4.3.10.1, and
msvc 9).

One speculation is that the code is illegal under the
current standard, but legal under c++0x. It is the
case that mingw g++ 4.5 and 4.6 print out the "correct"
result of "abc" when using the "-std=c++0x" option.
(mingw g++ 4.4 still prints out the "incorrect" pointer
value with "-std=c++0x".)

Here is a short test program and its output:


<< stringstream_test.cpp>>

#include<iostream>
#include<sstream>
#include<typeinfo>
int main (int argc, char *argv[]) {

std::string s1 =
dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();
std::cout<< "expecting abc: "<< s1<< std::endl; // prints out
"abc" as a pointer

std::stringstream sstr;
std::string s2 = dynamic_cast<std::stringstream&>(sstr<<
"xyz").str();
std::cout<< "expecting xyz: "<< s2<< std::endl;

}


C:\>g++ -o stringstream_test stringstream_test.cpp

C:\>stringstream_test
expecting abc: 0x477024
expecting xyz: xyz


That is, the two very similar test cases give different
results. The version with the unnamed temporary
stringstream prints out the "incorrect" pointer value,
while the version with the named local variable works
as expected.

(Again, more variations on this sample program that fail
to compile or give unexpected results can be found in the
thread on the MinGW Users List linked to above.)

So, is this code in error (and, if so, what's the specific
problem)? Or have we stumbled across a cluster of similar
bugs in a number of different compilers / libraries?

The only function possible is the member function operator<< with the
argument that is void*. The non-member operator<<(stream&, const
char*) cannot be used because there is no binding of a temporary to a
reference to non-const.

The behavior is as expected.

IIRC,

std::string s = (std::eek:stringstream() << std::flush << "abc").string();

behaves as the OP expected, at least on MSVC8 and gcc4.0.1 under XCode
3.1.2.

You meant .str(), not .string(), I am sure, and yes, it does.
Outputting 'std::flush' (which is a pointer to function) uses a member
op<<, which returns a ref to a non-const stream which then can be passed
to a non-member op<< to output the characters.

V
 
K

K. Frank

Hello Group!

First off, thanks to all who responded.


Yes, this explanation seems to be the consensus. As I understand it,
the code is perfectly legal, but, because of the temporary
stringstream,
operator<< resolves to the member function with argument void*, hence
printing out the pointer value.

I believe I understand what everyone is saying, and why this is the
behavior specified by the standard.

But there are still some details I don't understand...
...
IIRC,

  std::string s = (std::eek:stringstream() << std::flush << "abc").string();

behaves as the OP expected, at least on MSVC8 and gcc4.0.1 under XCode
3.1.2.

As Jeff says, adding std::flush causes the code to work as I had
expected, namely by printing out "abc".

My variant of the code is:

std::string s =
dynamic_cast<std::stringstream&>(std::stringstream() << std::flush <<
"abc").str();

Note, inserting other stuff in place of std::flush has the same
effect:

std::string s =
dynamic_cast<std::stringstream&>(std::stringstream() << std::skipws <<
"abc").str();

prints out "abc".

std::string s =
dynamic_cast<std::stringstream&>(std::stringstream() << 123 <<
"abc").str();

prints out "123abc".

std::string s =
dynamic_cast<std::stringstream&>(std::stringstream() << "xyz" <<
"abc").str();

prints out <pointer value>"abc".

(I tested these specifically with mingw g++ 4.4.1.)

I'm not sure exactly how the manipulators are processed, but as I
understand it, when first inserting 123 or "xyz", the member-function
operator<< is called, and then returns (*this). (*this) is still
the unnamed temporary, so the second insertion (<< "abc") should
again resolve to the member-function operator<< for void*, and so
should again print out the pointer value rather than "abc".

It's as if the first insertion operator causes the fact that we're
processing an unnamed temporary to be forgotten, and the second
insertion now resolves (incorrectly?) to the free-function operator<<
for char*, and prints out "abc" rather than the pointer value.

Is this now a compiler error, or is there another layer of explanation
that makes this the correct behavior?

Thanks Jeff for pointing out the std::flush variation, and thanks to
all
for any further insight.


K. Frank
 
V

Victor Bazarov

Hello Group!

First off, thanks to all who responded.



Yes, this explanation seems to be the consensus. As I understand it,
the code is perfectly legal, but, because of the temporary
stringstream,
operator<< resolves to the member function with argument void*, hence
printing out the pointer value.

I believe I understand what everyone is saying, and why this is the
behavior specified by the standard.

But there are still some details I don't understand...


As Jeff says, adding std::flush causes the code to work as I had
expected, namely by printing out "abc".

My variant of the code is:

std::string s =
dynamic_cast<std::stringstream&>(std::stringstream()<< std::flush<<
"abc").str();

Note, inserting other stuff in place of std::flush has the same
effect:

std::string s =
dynamic_cast<std::stringstream&>(std::stringstream()<< std::skipws<<
"abc").str();

prints out "abc".

std::string s =
dynamic_cast<std::stringstream&>(std::stringstream()<< 123<<
"abc").str();

prints out "123abc".

std::string s =
dynamic_cast<std::stringstream&>(std::stringstream()<< "xyz"<<
"abc").str();

prints out<pointer value>"abc".

(I tested these specifically with mingw g++ 4.4.1.)

I'm not sure exactly how the manipulators are processed, but as I
understand it, when first inserting 123 or "xyz", the member-function
operator<< is called, and then returns (*this). (*this) is still
the unnamed temporary, so the second insertion (<< "abc") should
again resolve to the member-function operator<< for void*, and so
should again print out the pointer value rather than "abc".

No. Unnamed temporary it is, but the call to the first member (to
output the number or the pointer) returns a reference to non-const,
which then is passed directly to the non-member. There is no binding of
a reference to a temporary after the first call.

Example:

struct A {
A& foo();
};

A& bar(A&);

int main() {
A().foo(); // OK
bar(A().foo()); // OK
bar(A()); // not OK - attempt to bind a non-const ref to a temp
}
It's as if the first insertion operator causes the fact that we're
processing an unnamed temporary to be forgotten, and the second
insertion now resolves (incorrectly?) to the free-function operator<<
for char*, and prints out "abc" rather than the pointer value.

No. The temporary is an object. It's temporary, but not constant. The
language rules require that when a reference is initialized from a
temporary, the reference has to be to a const object. But since the
temporary object is non-const, a non-const member function is allowed to
be called for it. That function can return a reference to non-const,
and you can initialize another non-const ref with that ref, and so on,
and use the object (and change it) *as long as* the temporary is still
*alive*.
Is this now a compiler error, or is there another layer of explanation
that makes this the correct behavior?

That's correct behaviour. You can call it a loophole in the language.
Thanks Jeff for pointing out the std::flush variation, and thanks to
all
for any further insight.


K. Frank

V
 
K

K. Frank

Hi Victor!

And thank you.

On 3/24/2011 4:18 PM, K. Frank wrote:
...

No.  Unnamed temporary it is, but the call to the first member (to
output the number or the pointer) returns a reference to non-const,
which then is passed directly to the non-member.  There is no binding of
a reference to a temporary after the first call.

Example:

     struct A {
        A& foo();
     };

     A& bar(A&);

     int main() {
        A().foo(); // OK
        bar(A().foo()); // OK
        bar(A()); // not OK - attempt to bind a non-const ref to a temp
     }


No.  The temporary is an object.  It's temporary, but not constant.  The
language rules require that when a reference is initialized from a
temporary, the reference has to be to a const object.  But since the
temporary object is non-const, a non-const member function is allowed to
be called for it.  That function can return a reference to non-const,
and you can initialize another non-const ref with that ref, and so on,
and use the object (and change it) *as long as* the temporary is still
*alive*.


That's correct behaviour.  You can call it a loophole in the language.

Thank you, Victor, for the clear explanation.

(I will admit that I've now taken a stroll through some alleys and
byways
I hadn't intended to tour.)

I have set myself a homework problem:


<< loophole.cpp >>

#include <iostream>

struct Loophole {
Loophole (int i) : i_(i) {}
Loophole &loophole() { return *this; }
int i_;
};

void print_incr_i (Loophole &l) {
l.i_++;
std::cout << "l.i_ = " << l.i_ << std::endl;
}

int main (int argc, char *argv[]) {
print_incr_i (Loophole (99).loophole());
// next line gives: error: invalid initialization of non-const
reference
print_incr_i (Loophole (66));
}


Does this correctly illustrate your explanation? As it stands, the
program fails to compile, with the error indicated in the comment.
But after commenting out the second call to print_incr_i, the program
compiles and prints out 100, as I would expect.

Even though legal, is this code "bad"? It does seem like I'm
sneaking around a restriction that the language purposely imposes.

And if this is bad, is using the temporary stringstream (with the
"<< std::flush" hack) also bad for the same reason?

Thanks again.


K. Frank
 
V

Victor Bazarov

[..]
(I will admit that I've now taken a stroll through some alleys and
byways
I hadn't intended to tour.)

I have set myself a homework problem:


<< loophole.cpp>>

#include<iostream>

struct Loophole {
Loophole (int i) : i_(i) {}
Loophole&loophole() { return *this; }
int i_;
};

void print_incr_i (Loophole&l) {
l.i_++;
std::cout<< "l.i_ = "<< l.i_<< std::endl;
}

int main (int argc, char *argv[]) {
print_incr_i (Loophole (99).loophole());
// next line gives: error: invalid initialization of non-const
reference
print_incr_i (Loophole (66));
}


Does this correctly illustrate your explanation? As it stands, the
program fails to compile, with the error indicated in the comment.

Yes, that's the expanded (and functional) version of what my 'foo' and
'bar' intended to show.
But after commenting out the second call to print_incr_i, the program
compiles and prints out 100, as I would expect.

Even though legal, is this code "bad"? It does seem like I'm
sneaking around a restriction that the language purposely imposes.

I don't think the code is bad. The connotation of a "loophole" is kind
of negative, but there is nothing seriously negative with this. Of
course, it is possible to write bad code with it. For instance,

int main() {
Loophole& hole = Loophole(99).loophole();
print_incr_i(hole);
}

What's happening here? Simple: undefined behaviour. The object created
temporarily (the result of the 'Loophole(99)' expression) only lives
until the semicolon that closes the declaration statement. The 'hole'
reference is only valid while it's being initialized. Right after
initialization it becomes invalid, and passing an invalid reference to
the 'print_incr_i' function (and using it inside) causes undefined
behaviour. A seasoned programmer will likely avoid this situation.
However, there can be another, more complicated case, more difficult to
recognise as dangerous. Consider:

#include <loophole.h> // Loophole and 'print_incr_i'

class Doughnut {
Loophole& myhole;
public:
Doughnut(Loophole& hole) : myhole(hole) {}
void roll() { print_incr_i(myhole); }
};

int main() {
Doughnut(Loophole(99).loophole()).roll(); // all is fine
}

In the example above the temporary 'Loophole' survives until 'roll()'
returns, which is OK. Now, somebody decides to hold onto the doughnut
and roll it as many times as they need:

int main() {
Doughnut creamy(Loophole(99).loophole());
for (int i = 0; i < 100; ++i)
creamy.roll();
}

As you imagine, it's the same problem as before. The reference held
inside the Doughnut object becomes invalid as soon as the object is done
initialising. Using that reference inside 'roll' has undefined behaviour.

Often, it's even more complex. Doughnuts would be created in the free
store in one function, where it's unknown the the Loophole is a
temporary, and used in another function. Consider:

void bake(Loophole& hole, Doughnut*& pTastyTreat) {
pTastyTreat = new Doughnut(hole);
}

void consume(Doughnut* pRoundThing) {
if (pRoundThing)
pRoundThing->roll();
}

int main() {
Loophole good_hole(42);
Doughnut *pTreat;
bake(good_hole, pTreat);
consume(pTreat); // all is good
delete pTreat;

bake(Loophole(666).loophole(), pTreat);
consume(pTreat); // KABOOM!!!
delete pTreat;
}

So, there are generally grades of "bad". If the code can potentially
create a maintenance problem, it's better avoided.
And if this is bad, is using the temporary stringstream (with the
"<< std::flush" hack) also bad for the same reason?

I think it's better than poking yourself in the eye with a sharp stick
(like one of my old friends liked to say). And it is important to
remember that every "loophole" has its edges which are better not crossed.

V
 
L

LR

Victor said:
You meant .str(), not .string(), I am sure, and yes, it does.
Outputting 'std::flush' (which is a pointer to function) uses a member
op<<, which returns a ref to a non-const stream which then can be passed
to a non-member op<< to output the characters.


Is this certain?


I've tried
----------------------------------------------------------------------
#include <sstream>
#include <string>

int main() {
const std::string s
= (std::eek:stringstream() << std::flush << "abc").str();
}
----------------------------------------------------------------------

Or very similar, with VS2008, GCC 4.5.2 and
http://www.comeaucomputing.com/tryitout/ see below for version, and all
three produced error messages similar to,


-----------------------------------------------------------------------
Comeau C/C++ 4.3.10.1 (Oct 6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
Copyright 1988-2008 Comeau Computing. All rights reserved.
MODE:strict errors C++ C++0x_extensions

"ComeauTest.c", line 5: error: class
"std::basic_ostream<char, std::char_traits<char>>" has no member
"str"
const std::string s = (std::eek:stringstream() << std::flush << "abc").str();
 
J

Jeff Flinn

Victor said:
Probably not :-/ My memory must not be as good as it used to be...

Same here, but I'm positive I stumbled on some combination that had
worked. :-(
 
G

Gerhard Fiedler

K. Frank said:
Even though legal, is this code "bad"? It does seem like I'm sneaking
around a restriction that the language purposely imposes.

And if this is bad, is using the temporary stringstream (with the "<<
std::flush" hack) also bad for the same reason?

Thanks to you and Victor for this instructive thread. My take is that
whenever you handle references (or pointers), keeping an eye on the
lifetime of the referenced (or pointed to) object is important; it
should not be destroyed before the references (or pointers, or resetting
the pointers).

Anything that makes this "keeping an eye on the lifetime" difficult is,
sort of, "bad". At the very least I think that these "loophole
techniques" need /very/ good documentation about what they do and how
they are intended to be used.

Gerhard
 
J

James Kanze

Is this certain?
I've tried
int main() {
const std::string s
= (std::eek:stringstream() << std::flush << "abc").str();}

Or very similar, with VS2008, GCC 4.5.2 andhttp://www.comeaucomputing.com/tryitout/see below for version, and all
three produced error messages similar to,
-----------------------------------------------------------------------
Comeau C/C++ 4.3.10.1 (Oct 6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
Copyright 1988-2008 Comeau Computing. All rights reserved.
MODE:strict errors C++ C++0x_extensions

"ComeauTest.c", line 5: error: class
"std::basic_ostream<char, std::char_traits<char>>" has no member
"str"
const std::string s = (std::eek:stringstream() << std::flush << "abc").str();
-----------------------------------------------------------------------

The (static) result type of (std::eek:stringstream() << std::flush
<< "abc") is ostream&, which doesn't have an str() function.
You have to cast this back to ostringstream (the dynamic type)
to use str() on it, e.g.:

std::string s =
static_cast<std::eek:stringstream&>(
std::eek:stringstream() << std::flush << "abc").str();
 
K

K. Frank

Hi Group!

First off, thanks to Victor and the other respondents for
sorting this out for me.

I have a follow-up question concerning this issue and the
draft c++0x standard.

[..]
(I will admit that I've now taken a stroll through some alleys and
byways
I hadn't intended to tour.)
I have set myself a homework problem:
<<  loophole.cpp>> ...
Does this correctly illustrate your explanation?  As it stands, the
program fails to compile, with the error indicated in the comment.

Yes, that's the expanded (and functional) version of what my 'foo' and
'bar' intended to show.
...
V

I have a copy of the draft standard, but I don't have a
copy of the current standard, so I can't make a detailed
comparison to see what changed.

However, as I understand it, under the new draft, the issue
with operator<< is still the same: operator<< for char* is
still a free function, while operator<< for void* is a
member function. But it appears that the restriction about
binding a temporary to a non-const reference may have been
relaxed.

As Kai pointed out in the MinGW thread, section 12.2.5 of
the draft standard talks about binding temporaries to
references without imposing the restriction that the
references be const. Our reading suggests that the basic
example:

std::string s1 =
dynamic_cast<std::stringstream&>(std::stringstream() << "abc").str();

is legal under the draft standard and should print out
"abc" (rather than the pointer value).

Now the conundrum.

(First, I understand that g++ doesn't claim to implement
completely, or even correctly, the draft standard. But
at least it's a start.)

Compiling

std::string s1 =
dynamic_cast<std::stringstream&>(std::stringstream() << "abc").str();

with "g++ -std=c++0x" (version 4.5.2) prints out "abc".
My interpretation is that the draft standard lets the
temporary bind to the ostream& reference argument of the
free function operator<< for char*, so that the call no
longer resolves to the member function operator<< for void*.

So far, so good (I think)...

However, compiling the loophole example:


<< loophole.cpp >>

#include <iostream>

struct Loophole {
Loophole (int i) : i_(i) {}
Loophole &loophole() { return *this; }
int i_;

};

void print_incr_i (Loophole &l) {
l.i_++;
std::cout << "l.i_ = " << l.i_ << std::endl;

}

int main (int argc, char *argv[]) {
print_incr_i (Loophole (99).loophole());
// next line gives: error: invalid initialization of non-const
reference
print_incr_i (Loophole (66));

}


with "g++ -std=c++0x" still gives the compile error:

loophole.cpp: In function 'int main(int, char**)':
loophole.cpp:17:30: error: invalid initialization of non-const
reference of type 'Loophole&' from an rvalue of type 'Loophole'
loophole.cpp:9:6: error: in passing argument 1 of 'void
print_incr_i(Loophole&)'

So, the theory was that the draft standard relaxes the
restriction on binding a temporary to a non-const reference
(and that "g++ -std=c++0x" implements this change), and
that's why the stringstream example works. But the
loophole example contradicts this.

How should the two examples (stringstream and loophole)
behave under the draft of the c++0x standard? Is g++
right in both cases (for reasons that I don't understand),
or is something half-baked going on here?


Again, thanks to all for any further insight.


K. Frank
 
S

SG

I have a follow-up question concerning this issue and the
draft c++0x standard.
[...]

C++0x does not allow you to bind temporaries to non-const lvalue
references. But its standard library provides "rvalue stream
insertion" (§27.7.2.9):

template <class charT, class traits, class T>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>&& os, const T& x);

Effect : os << x
Returns: os

Note the double ampersand (&&). This is an rvalue reference.
Basically, it only binds to rvalues and you can use it during
overloading to distinguish between lvalues and rvalues. So, your
temporary is bound to this rvalue reference called 'os'. 'os' will be
an lvalue expression referring to your stream object (since it is a
named reference), 'os << x' will call the free operator<< that takes
an lvalue stream reference and a pointer of type const char*.

SG
 
K

K. Frank

Hi SG!

Thank you; that clears things up nicely.

I have a follow-up question concerning this issue and the
draft c++0x standard.
[...]

C++0x does not allow you to bind temporaries to non-const lvalue
references. But its standard library provides "rvalue stream
insertion" (§27.7.2.9):

  template <class charT, class traits, class T>
    basic_ostream<charT, traits>&
    operator<<(basic_ostream<charT, traits>&& os, const T& x);

  Effect : os << x
  Returns: os

Note the double ampersand (&&). This is an rvalue reference.
Basically, it only binds to rvalues and you can use it during
overloading to distinguish between lvalues and rvalues. So, your
temporary is bound to this rvalue reference called 'os'. 'os' will be
an lvalue expression referring to your stream object (since it is a
named reference), 'os << x' will call the free operator<< that takes
an lvalue stream reference and a pointer of type const char*.

Very good. This all makes sense now. The meaning / purpose of the
new ostream.rvalue section hadn't really registered with me.

By the way, from what I can tell the purpose of ostream.rvalue is
to support the use of temporary ostreams in the kind of on-the-fly
construction I was playing around with that led off this discussion.
See, for example, the section titled "1203. More useful rvalue stream
insertion" of:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2948.html


To check my understanding, I modified my loophole example to include
an overloaded version of print_incr_i that takes an rvalue reference
argument:

void print_incr_i (Loophole &&l) {
l.i_ += 2;
std::cout << "l.i_ = " << l.i_ << std::endl;
}

(Note, this version of the function increments i_ by two, so that
I can get verification of which overloaded function is called in
which situation.)

This modified loophole example now compiles (with -std=c++0x), and
runs as expected in that the rvalue-reference version of the
overloaded
function is called for:

print_incr_i (Loophole (66));

Thanks again for your explanation.


K. Frank
 
S

SG

Thanks again for your explanation.

I should probably mention that there is more to && than its ability to
bind to temporaries. Not knowing more about && might lead to bad
surprizes. So, here are some more things you ought to know about
rvalue references:

For some object type T the type T&& will be an rvalue reference. It's
like an lvalue reference with two differences: the rules on how such a
reference can be initialized are different (obviously) and a function
call with an rvalue reference return type is considered an rvalue
expression (unlike the name of an rvalue reference).

For some reference type T the type T&& is not necessarily an rvalue
reference anymore. If T is an lvalue reference, then so is T&&. This
is called "reference collapsing". Examples:

T T&& T&
------------------
int int&& int&
int&& int&& int&
int& int& int&

Finally, there is a funny deduction rule that only applies to the
pattern T&& where T is a template parameter like in case #2:

template<class T> void bar(T const&) {} // #1

template<class T> void bar(T && r) { // #2
r = 0; // modifying temporaries does not hurt nobody, right?
}

int main() {
int i = 1729;
bar(99); // #2 with T=int, T&&=int&&
bar(i); // #2 with T=int&, T&&=int&
assert(i==1729); // test will FAIL!
}

In both calls #2 is picked because it is a better match:

call #1 #2
-----------------------------------------------
bar(99) bar<int>(int const&) bar<int>(int&&)
bar(i) bar<int>(int const&) bar<int&>(int&)

This is what I meant by bad surprizes. The purpose of this deduction
rule is to retain the information about the function argument's value
category as part of the type parameter. The "perfect forwarding"
technique relies on this behaviour.

SG
 
K

K. Frank

Hello SG!

Thank you for the further discussion.

I should probably mention that there is more to && than its ability to
bind to temporaries. Not knowing more about && might lead to bad
surprizes. So, here are some more things you ought to know about
rvalue references:
...

This is all very helpful. I am trying -- step by step, and over
time -- to understand rvalue references and move semantics as I
work to get on board with the new standard.

I appreciate your explaining some of the details of rvalue references
and the consequences they can have.


Best regards.


K. Frank
 
H

Howard Hinnant

Hello SG!

Thank you for the further discussion.




This is all very helpful.  I am trying -- step by step, and over
time -- to understand rvalue references and move semantics as I
work to get on board with the new standard.

I appreciate your explaining some of the details of rvalue references
and the consequences they can have.

It isn't perfect, but here is a brief tutorial on the subject:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html

This paper was meant to be a tutorial for the committee to ease
concerns on adopting it into the working draft.

-Howard
 
K

K. Frank

Hi Howard!

Thank you for the link to your tutorial.

...
It isn't perfect, but here is a brief tutorial on the subject:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html

This paper was meant to be a tutorial for the committee to ease
concerns on adopting it into the working draft.

This is very helpful. In particular, I appreciate that you include
some of the "why" along with the "what" (something that is rather
lacking in the standard).

This is a good start for me on rvalue references. I should say, I
am very much looking forward to the new standard becoming official,
and, of course, to more complete compiler support for it, as well.

Best.


K. Frank
 

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

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,540
Members
45,025
Latest member
KetoRushACVFitness

Latest Threads

Top