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

N3090: Rvalue reference example in 8.5.3/5 correct or wrong?

167 views
Skip to first unread message

SG

unread,
Jun 17, 2010, 2:00:25 PM6/17/10
to
Hello !

I just saw this code example in N3090.pdf (see 8.5.3/5):

> int i = 2;
> =85
> double&& rd4 = i; // error: rvalue reference cannot bind to lvalue

It was an example from the last bullet point about initializing
references:

> Otherwise, the reference shall be an lvalue reference to a
> non-volatile const type (i.e., cv1 shall be const), or the reference
> shall be an rvalue reference and the initializer expression shall be
> an rvalue or have a function type.

I actually expected the initialization of rd4 to work. It was my
understanding that i is first converted to a double which results in
an rvalue and should bind to the rvalue reference.

I think this example including the text above it is not intentional.
The text seems to rule out things like

void foo(string&&);
=85
void bar() {
foo("hello");
}

because the initializer is not an rvalue. It is something that is not
reference-compatible but convertible to a string which yields an
rvalue.

So, what's the deal here? Did I miss something? Is this intentional or
an error?

Cheers!
SG


--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use
mailto:std...@netlab.cs.rpi.edu<std-c%2B%2...@netlab.cs.rpi.edu>
]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]

Daniel Krügler

unread,
Jun 17, 2010, 5:14:22 PM6/17/10
to
On 17 Jun., 20:00, SG <s.gesem...@gmail.com> wrote:
> Hello !
>
> I just saw this code example in N3090.pdf (see 8.5.3/5):
>
> > int i = 2;
> > =85
> > double&& rd4 = i; // error: rvalue reference cannot bind to lvalue
>
> It was an example from the last bullet point about initializing
> references:
>
> > Otherwise, the reference shall be an lvalue reference to a
> > non-volatile const type (i.e., cv1 shall be const), or the reference
> > shall be an rvalue reference and the initializer expression shall be
> > an rvalue or have a function type.
>
> I actually expected the initialization of rd4 to work. It was my
> understanding that i is first converted to a double which results in
> an rvalue and should bind to the rvalue reference.
>
> I think this example including the text above it is not intentional.

It's hard to say - AFAIK, it is not a blatant oversight, because
there
are different views on this. The positive side of the current state
is,
that initializing an rvalue reference *usually* does not implicitly
create a temporary to which the reference is bound - harrumph,
except if it does:

struct A { };
struct B { operator A(); };
B foo();
A&& aref = foo();

;-)

In fact, an earlier posting

http://groups.google.de/group/comp.std.c++/msg/dba9735c82ec69ef

highlights some similar weaknesses of the current model
as you do. I'm not sure whether an NB comment exists.

> The text seems to rule out things like
>
> void foo(string&&);
> =85
> void bar() {
> foo("hello");
> }
>
> because the initializer is not an rvalue. It is something that is not
> reference-compatible but convertible to a string which yields an
> rvalue.
>
> So, what's the deal here? Did I miss something? Is this intentional or
> an error?

It is not really an error, but the current state might have more
disadvantages than advantages. It would be good, if more
opinions/examples could be collected to decide what the
best end result would be (including consistency and efficiency).
Show stoppers for either either choice would be the best, but
even convincing tendencies would help.

The alternative perspective of a working mental model would be,
if the rules would concentrate on the immediately bound value
being an rvalue, and if they would not concentrate on the
rvalueness of the direct expression, as you suggest.

HTH & Greetings from Bremen,

Daniel Kr�gler

--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]

[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]

SG

unread,
Jun 19, 2010, 4:38:00 AM6/19/10
to
On 17 Jun., 23:14, Daniel Kr�gler wrote:
> On 17 Jun., 20:00, SG wrote:
> > [...]

> It is not really an error, but the current state might have more
> disadvantages than advantages. It would be good, if more
> opinions/examples could be collected to decide what the
> best end result would be (including consistency and efficiency).
> Show stoppers for either either choice would be the best, but
> even convincing tendencies would help.

About consistency: I basically expected T&& to be similar to const T&
with respect to creating temporaries.

void foo(const string&);
void g() { foo("hello"); }

creates a temporary string and binds a reference-to-const to it.
According to the rules an additional overload for foo with an rvalue
reference won't help here because "hello" is not an lvalue. I have a
hard time imagining this as intentional behaviour. GCC 4.4 actually
picks the rvalue reference overload in this case which apparently is
not supposed to work.

The general example would be:
- there's an overload const T&, T&&
- argument is an lvalue of type U
- T and U are not reference-related
- U is implicitly convertible to T
Isn't it obvious that the T&& overload for the temporary should be
selected?

> The alternative perspective of a working mental model would be,
> if the rules would concentrate on the immediately bound value
> being an rvalue, and if they would not concentrate on the
> rvalueness of the direct expression, as you suggest.

Right. I'm not suggesting that a temporary should always be created. I
just think that T&& should act like const T& w.r.t. creating
temporaries. If the type of the reference and the type of the
initializer are already reference-related and the initializer is an
lvalue we don't need the compiler to create a temporary just so the
rvalue reference could bind to it. If that were the case there'd be
hardly a difference between pass-by-value and pass-by-rvalue-
reference.

Cheers!
SG

Daniel Krügler

unread,
Jun 19, 2010, 7:18:56 PM6/19/10
to
On 19 Jun., 10:38, SG <s.gesem...@gmail.com> wrote:
> On 17 Jun., 23:14, Daniel Kr gler wrote:
>
> > On 17 Jun., 20:00, SG wrote:
> About consistency: I basically expected T&& to be similar to const T&
> with respect to creating temporaries.
>
> void foo(const string&);
> void g() { foo("hello"); }
>
> creates a temporary string and binds a reference-to-const to it.
> According to the rules an additional overload for foo with an rvalue
> reference won't help here because "hello" is not an lvalue.

I assume here that you meant: 'because "hello" is an lvalue'

> I have a hard time imagining this as intentional behaviour. GCC 4.4
> actually picks the rvalue reference overload in this case which
> apparently is not supposed to work.

Yes, this is a known compiler-bug according to the FCD
and the question is, which is the most appropriate rule. The
compiler behaves according to the mental model, which is
similar to which you are preferring. It might be the one, the
majority will prefer.

> The general example would be:
> - there's an overload const T&, T&&
> - argument is an lvalue of type U
> - T and U are not reference-related
> - U is implicitly convertible to T
> Isn't it obvious that the T&& overload for the temporary should be
> selected?

Sounds reasonable, yes ;-)

> > The alternative perspective of a working mental model would be,
> > if the rules would concentrate on the immediately bound value
> > being an rvalue, and if they would not concentrate on the
> > rvalueness of the direct expression, as you suggest.
>
> Right. I'm not suggesting that a temporary should always be created. I
> just think that T&& should act like const T& w.r.t. creating
> temporaries. If the type of the reference and the type of the
> initializer are already reference-related and the initializer is an
> lvalue we don't need the compiler to create a temporary just so the
> rvalue reference could bind to it. If that were the case there'd be
> hardly a difference between pass-by-value and pass-by-rvalue-
> reference.

I need more time to think about this.

Thanks for your opinion,

- Daniel


--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use

mailto:std...@netlab.cs.rpi.edu<std-c%2B%2...@netlab.cs.rpi.edu>

Douglas Turk

unread,
Jun 22, 2010, 7:01:52 PM6/22/10
to

> > The alternative perspective of a working mental model would be,
> > if the rules would concentrate on the immediately bound value
> > being an rvalue, and if they would not concentrate on the
> > rvalueness of the direct expression, as you suggest.
>
> Right. I'm not suggesting that a temporary should always be created. I
> just think that T&& should act like const T& w.r.t. creating
> temporaries. If the type of the reference and the type of the
> initializer are already reference-related and the initializer is an
> lvalue we don't need the compiler to create a temporary just so the
> rvalue reference could bind to it. If that were the case there'd be
> hardly a difference between pass-by-value and pass-by-rvalue-
> reference.
>
I agree that the temporary binding rules should be amended as you say,
because it also doesn't match my mental model. In cases like this, I
generally try to find out if there is some reason for the standardized
behavior, but it doesn't seem like there is one here.

As you say, changing the reference binding rules so that lvalues of a
type could bind to rvalue refs of that type (either directly, or with
an implicit copy) would make the behavior of rvalue refs very similar
to values, for types that are both copyable and cheaply movable. But,
there would still be significant differences for copyable-but-not-
movable types - this includes all legacy C++03 types.

For example, consider code like this:

//Old (C++03) definition, in a library that can't be changed
class old_string_type
{
public:
old_string_type(); //cheap, O(1)
old_string_type(const old_string_type &); //Potentially O(size())

//No move c'tor declared - this is C++03 code.

void swap(old_string_type &); //cheap, O(1)
old_string_type(const char *); //various implicit conversions
};

//New C++0x function, not part of the library.

void append_strings(std::vector<old_string_type> & v, old_string_type
&& s1, old_string_type && s2)
{
//Take advantage of the temporary nature of s1 and s2 for increased
efficiency.
v.push_back(old_string_type());
v.back().swap(s1);
v.push_back(old_string_type());
v.back().swap(s2);
}
//(Possible additional overloads of append_strings?)

//Example calling code
append_strings(v1, "abc", "def"); //Does this make redundant copies?
append_strings(v1, old_string_lvalue, old_string_lvalue); //Is this
safe? Do we need additional overloads of append_strings that accept
const lvalue refs?
append_strings(v1, "abc", old_string_rvalue_variable); //Is this an
error? Maybe std::move(old_string_rvalue_variable) was meant.

If the reference binding rules were to be revised, one might consider
whether this type of code should be acceptable or unacceptable, and
how it should behave, or how it should be modified.

Regards,
Douglas Turk

--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use

mailto:std...@netlab.cs.rpi.edu<std-c%2B%2...@netlab.cs.rpi.edu>

SG

unread,
Jun 23, 2010, 3:14:30 PM6/23/10
to
On 23 Jun., 01:01, Douglas Turk wrote:
>
> As you say, changing the reference binding rules so that lvalues of a
> type could bind to rvalue refs of that type (either directly, or with
> an implicit copy) would make the behavior of rvalue refs very similar
> to values, for types that are both copyable and cheaply movable. But,
> there would still be significant differences for copyable-but-not-
> movable types - this includes all legacy C++03 types.
>
> For example, consider code like this:
>
> //Old (C++03) definition, in a library that can't be changed
> class old_string_type
> {

[snip, string with no move operation]

> //New C++0x function, not part of the library.
> void append_strings(std::vector<old_string_type> & v,
> old_string_type && s1, old_string_type && s2)
> {
> //Take advantage of the temporary nature of s1 and s2

> //for increased efficiency.
> v.push_back(old_string_type());
> v.back().swap(s1);
> v.push_back(old_string_type());
> v.back().swap(s2);
> }

Assuming a compiler that's capable of doing basic copy elisions, I
don't see the need here for taking the strings via rvalue reference.
Can you explain your point/example?

Cheers!
SG

Douglas Turk

unread,
Jun 26, 2010, 9:20:40 PM6/26/10
to
On Jun 24, 5:14 am, SG <s.gesem...@gmail.com> wrote:
> On 23 Jun., 01:01, Douglas Turk wrote:
>
>
>
>
> Assuming a compiler that's capable of doing basic copy elisions, I
> don't see the need here for taking the strings via rvalue reference.
> Can you explain your point/example?
>

On second reading, it does seem like I could have explained that more
clearly :).

Copy elisions are allowed by the standard in a number of predefined
places, but they don't cover every possible place where a programmer
might want to move instead of doing an unnecessary copy. Although
compilers could, in theory, optimize away some unnecessary copies
under the as-if rule, it's a pretty big ask, and I would think it's
impossible to write a compiler that could remove unnecessary copies in
absolutely every case. Ultimately, this is (I assume) the reason for
introducing move constructors, std::move, and the associated types of
rvalue references into the language, rather than just mandating that
copy elision is always performed. With std::move, the programmer can
explicitly indicate when they are no longer interested in the contents
of a variable.

One of the places where copy elisions are not permitted is when
passing an lvalue to a function that takes its argument by value. One
of the example calls I wrote in my previous post did this, but I
didn't explain what I was trying to say there.

Consider what the effect would be if the append_strings example were
declared with value arguments:

void append_strings(std::vector<old_string_type> & v, old_string_type

s1, old_string_type s2)
{
/*Same implementation as before*/
}

...and then called like this:

old_string_type s1, s1;
/*do some computations that put some useful data into s1, s2*/
append_strings(v, std::move(s1), std::move(s2));

Although the calling code doesn't want s1 and s2 any more, and
append_strings knows how to to efficiently swap in the unwanted
values, there's still an unnecessary copy made as part of the argument
binding, because std::move'ing a copy-only type like old_string_type
has no net effect here. If append_strings accepted rvalue references,
on the other hand, this extra copy could be avoided. This is the
difference between passing a copy-only type by value and by rvalue
reference.

My point was that to avoid this extra copy, and have append_strings
work as if old_string_type was a movable and copyable type passed by
value, you'd also have to write four overload combinations accepting
every combination of const old_string_type &/old_string_type&&. This
also affects any generic code that might want to deal with C++03
copyable-but-not-movable types without redundant copies, like generic
containers.

Anyway, this is hopefully a bit of a better explanation of what I
meant when I said that passing by value and by rvalue reference would
still be different.

Regards,
Douglas Turk

--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]

[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]

0 new messages