Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Bad use of stringstream temporary?

332 views
Skip to first unread message

K. Frank

unread,
Mar 24, 2011, 12:08:53 PM3/24/11
to
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

Leigh Johnston

unread,
Mar 24, 2011, 12:32:06 PM3/24/11
to

Obviously all three compilers are behaving correctly. The operator<<
that takes a void* is a member function so will work with a temporary
whilst the operator<< which takes a char* is a free function that
requires a non-const reference to a stream which will obviously not bind
to a temporary.

HTH.

/Leigh

SG

unread,
Mar 24, 2011, 12:41:54 PM3/24/11
to
On 24 Mrz., 17:08, K. Frank wrote:
>
> 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();

static_cast is fine in this case.

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

I could see why this happens. There are a couple of overloads for
operator<<. Some of them are member functions (which can be called
even on a temporary) and some of them are free functions which take a
non-const reference (and hence require an lvalue, lvalue !=
temporary).

As it turns out operator<<(ostream&, const char*) is a free function
which won't be part of the overload set since you cannot bind a
temporary to this non-const lvalue reference. On the other hand,
ostream::operator<<(const void*) is a member function and a viable
candidate. So, this is what happens:
- array-to-pointer standard conversion (const char[4] --> const char*)
- implicit pointer conversion (const char* --> const void*)
- member function taking the const void* as argument is selected

To avoid this issue and to shorten the code you could wrap a
stringstream object in a custom class type:

struct any2str {
std::stringstream ss;
template<class T>
any2str& operator<<(T const& x) {ss<<x; return *this;}
operator std::string() const {return ss.str();}
};

std::string s1 = any2str() << "abc";

SG

Victor Bazarov

unread,
Mar 24, 2011, 12:44:11 PM3/24/11
to

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
--
I do not respond to top-posted replies, please don't ask

Jeff Flinn

unread,
Mar 24, 2011, 3:13:24 PM3/24/11
to

IIRC,

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

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

Jeff

Victor Bazarov

unread,
Mar 24, 2011, 3:52:04 PM3/24/11
to

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.

K. Frank

unread,
Mar 24, 2011, 4:18:45 PM3/24/11
to
Hello Group!

First off, thanks to all who responded.

On Mar 24, 3:13 pm, Jeff Flinn <TriumphSprint2...@hotmail.com> wrote:
> Victor Bazarov wrote:
> > On 3/24/2011 12:08 PM, K. Frank wrote:
> >> 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".

> ...


> > 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.

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::ostringstream() << 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?

>
> Jeff

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


K. Frank

Victor Bazarov

unread,
Mar 24, 2011, 4:28:18 PM3/24/11
to

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.

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

V

K. Frank

unread,
Mar 24, 2011, 5:50:09 PM3/24/11
to
Hi Victor!

And thank you.

On Mar 24, 4:28 pm, Victor Bazarov <v.baza...@comcast.invalid> wrote:
> On 3/24/2011 4:18 PM, K. Frank wrote:

> ...


> >> Victor Bazarov wrote:
> >>> On 3/24/2011 12:08 PM, K. Frank wrote:
> >>>> 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();

> ...


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

> ...


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

> ...

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?

> V

Thanks again.


K. Frank

Victor Bazarov

unread,
Mar 24, 2011, 7:27:54 PM3/24/11
to
On 3/24/2011 5:50 PM, K. Frank wrote:
> [..]

> (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.

LR

unread,
Mar 24, 2011, 8:34:20 PM3/24/11
to


Is this certain?


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

int main() {
const std::string s
= (std::ostringstream() << 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::ostringstream() << std::flush << "abc").str();
-----------------------------------------------------------------------

LR

Victor Bazarov

unread,
Mar 24, 2011, 9:43:30 PM3/24/11
to
On 3/24/2011 8:34 PM, LR wrote:
> Victor Bazarov wrote:
>> On 3/24/2011 3:13 PM, Jeff Flinn wrote:
>>> Victor Bazarov wrote:
>>>> On 3/24/2011 12:08 PM, K. Frank wrote:
>
>>> IIRC,
>>>
>>> std::string s = (std::ostringstream()<< 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.
>
>
> Is this certain?

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

>
>
> I've tried
> ----------------------------------------------------------------------
> #include<sstream>
> #include<string>
>
> int main() {
> const std::string s
> = (std::ostringstream()<< 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::ostringstream()<< std::flush<< "abc").str();
> -----------------------------------------------------------------------
>
> LR
>

Jeff Flinn

unread,
Mar 25, 2011, 7:44:39 AM3/25/11
to
Victor Bazarov wrote:
> On 3/24/2011 8:34 PM, LR wrote:
>> Victor Bazarov wrote:
>>> On 3/24/2011 3:13 PM, Jeff Flinn wrote:
>>>> Victor Bazarov wrote:
>>>>> On 3/24/2011 12:08 PM, K. Frank wrote:
>>
>>>> IIRC,
>>>>
>>>> std::string s = (std::ostringstream()<< 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.
>>
>>
>> Is this certain?
>
> 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. :-(

Gerhard Fiedler

unread,
Mar 25, 2011, 10:12:17 AM3/25/11
to
K. Frank wrote:

> 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

James Kanze

unread,
Mar 26, 2011, 9:56:32 AM3/26/11
to
On Mar 25, 12:34 am, LR <lr...@superlink.net> wrote:
> Victor Bazarov wrote:
> > On 3/24/2011 3:13 PM, Jeff Flinn wrote:
> >> Victor Bazarov wrote:
> >>> On 3/24/2011 12:08 PM, K. Frank wrote:
> >> IIRC,

> >> std::string s = (std::ostringstream() << 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.

> Is this certain?

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

> int main() {
> const std::string s
> = (std::ostringstream() << 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::ostringstream() << std::flush << "abc").str();
> -----------------------------------------------------------------------

The (static) result type of (std::ostringstream() << 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::ostringstream&>(


std::ostringstream() << std::flush << "abc").str();

--
James Kanze

K. Frank

unread,
Mar 26, 2011, 1:58:59 PM3/26/11
to
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.

On Mar 24, 7:27 pm, Victor Bazarov <v.baza...@comcast.invalid> wrote:
> On 3/24/2011 5:50 PM, K. Frank wrote:
>
> > [..]
> > (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

SG

unread,
Mar 26, 2011, 2:38:30 PM3/26/11
to
On 26 Mrz., 18:58, K. Frank wrote:
>
> 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. Frank

unread,
Mar 26, 2011, 7:06:17 PM3/26/11
to
Hi SG!

Thank you; that clears things up nicely.

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));

>
> SG

Thanks again for your explanation.


K. Frank

SG

unread,
Mar 27, 2011, 8:57:19 AM3/27/11
to
On 27 Mrz., 01:06, K. Frank wrote:
> 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. Frank

unread,
Mar 27, 2011, 12:39:25 PM3/27/11
to
Hello SG!

Thank you for the further discussion.

On Mar 27, 8:57 am, SG <s.gesem...@gmail.com> wrote:
> On 27 Mrz., 01:06, K. Frank wrote:
>
> > 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:

> ...

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

Howard Hinnant

unread,
Mar 27, 2011, 3:28:22 PM3/27/11
to

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. Frank

unread,
Mar 27, 2011, 4:03:34 PM3/27/11
to
Hi Howard!

Thank you for the link to your tutorial.

On Mar 27, 3:28 pm, Howard Hinnant <howard.hinn...@gmail.com> wrote:
> On Mar 27, 12:39 pm, "K. Frank" <kfrank2...@gmail.com> wrote:

> ...


> > On Mar 27, 8:57 am, SG <s.gesem...@gmail.com> wrote:

> ...


> > > 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.

> ...


> 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.

> -Howard

Best.


K. Frank

Howard Hinnant

unread,
Mar 28, 2011, 9:31:34 AM3/28/11
to
On Mar 27, 4:03 pm, "K. Frank" <kfrank2...@gmail.com> wrote:
> 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.

While not exactly official, the C++ committee voted out an FDIS (Final
Draft International Standard) 3 days ago (March 25, 2011) in Madrid.
That means technical work is done and we expect the bureaucratic work
for the official stamp to be done (hopefully) within a handful of
months. We're talking C++11. :-) The final draft should appear in
the post-Madrid mailing within a couple of weeks (after intense and
much appreciated work done by the project editor Pete Becker to
incorporate the changes from Madrid, and a small review committee).

-Howard

0 new messages