Discussion:
Converting lvalue to rvalue
(too old to reply)
Vlad from Moscow
2015-07-03 07:40:18 UTC
Permalink
MS VC++ has the following bug shown in this demonstrative program

#include <iostream>

int main()
{
int x;
int{ x } = 10;
std::cout << "x = " << x << std::endl;
}

The expression with explicit type conversion using functional notation int{
x } does not create a temporary object. The compiler compiles the code and
the program outputs 10.

Now a question arises: what is the simplest way to convert an lvalue to an
rvalue?

For example it could be written like x + 0 or +x. However such expressions
can look unclear for the reader of the code.

The necessity of such a conversion exists when an lvalue is used as an
ergument for a parameter declared as a constant reference and it is
desirable that the reference would be bind with a temporary object instead
of the lvalue.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Krauss
2015-07-03 07:48:43 UTC
Permalink
Post by Vlad from Moscow
MS VC++ has the following bug shown in this demonstrative program
#include <iostream>
int main()
{
int x;
int{ x } = 10;
std::cout << "x = " << x << std::endl;
}
The expression with explicit type conversion using functional notation int{ x } does not create a temporary object. The compiler compiles the code and the program outputs 10.
Maybe so, but it would be better demonstrated without the UB. Why not give x an initializer at its definition?
Post by Vlad from Moscow
Now a question arises: what is the simplest way to convert an lvalue to an rvalue?
For example it could be written like x + 0 or +x. However such expressions can look unclear for the reader of the code.
You can create a function for this purpose.

template< typename t >
t val( t const & o ) { return o; }

template< typename t >
t && val(t && o ) {
static_assert ( ! std::is_reference< t >::value, "unexpected forwarding reference." );
return std::move( o );
}
Post by Vlad from Moscow
The necessity of such a conversion exists when an lvalue is used as an ergument for a parameter declared as a constant reference and it is desirable that the reference would be bind with a temporary object instead of the lvalue.
I think there are other applications, which don’t come to mind at the moment.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
'Johannes Schaub' via ISO C++ Standard - Discussion
2015-07-03 09:15:14 UTC
Permalink
Post by David Krauss
Post by Vlad from Moscow
MS VC++ has the following bug shown in this demonstrative program
#include <iostream>
int main()
{
int x;
int{ x } = 10;
std::cout << "x = " << x << std::endl;
}
The expression with explicit type conversion using functional notation
int{ x } does not create a temporary object. The compiler compiles the code
and the program outputs 10.
Post by David Krauss
Maybe so, but it would be better demonstrated without the UB. Why not
give x an initializer at its definition?
Post by David Krauss
Post by Vlad from Moscow
Now a question arises: what is the simplest way to convert an lvalue to an rvalue?
For example it could be written like x + 0 or +x. However such
expressions can look unclear for the reader of the code.
Post by David Krauss
You can create a function for this purpose.
template< typename t >
t val( t const & o ) { return o; }
template< typename t >
t && val(t && o ) {
static_assert ( ! std::is_reference< t >::value, "unexpected forwarding reference." );
return std::move( o );
}
Should this not select the lower overload for nonconst lvalues?
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Krauss
2015-07-03 09:28:33 UTC
Permalink
Post by 'Johannes Schaub' via ISO C++ Standard - Discussion
Should this not select the lower overload for nonconst lvalues?
It shouldn’t, but I guess it does :( . At least the static_assert is there :) . I’ve been through this exercise several times already, bah.

Personally, when I actually needed it, I sacrificed efficiency for simplicity:

template< typename t >
std::decay_t< t > val( t && v )
{ return std::forward< t >( v ); }

And, I recall why now: it was to pass lvalue arguments to a function that needed an rvalue. That was a questionable design and eventually I got rid of it.

Perhaps it’s useful if you’re really trying to hand out copies of something.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Daniel Krügler
2015-07-03 17:50:49 UTC
Permalink
On 2015–07–03, at 5:15 PM, 'Johannes Schaub' via ISO C++ Standard -
Should this not select the lower overload for nonconst lvalues?
It shouldn’t, but I guess it does :( . At least the static_assert is there
:) . I’ve been through this exercise several times already, bah.
template< typename t >
std::decay_t< t > val( t && v )
{ return std::forward< t >( v ); }
And, I recall why now: it was to pass lvalue arguments to a function that
needed an rvalue. That was a questionable design and eventually I got rid of
it.
Yes, we really should standardize decay_copy:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3255.html

- Daniel
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Krauss
2015-07-05 05:37:53 UTC
Permalink
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3255.html <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3255.html>
Reflecting on it more, it’s an anti-pattern and a dead end.

1. The paper doesn’t show how decay_copy solves its own example. It would appear that either std::decay or std::make_pair, which encapsulates std::decay, would be necessary and sufficient.

template< typename T >
void g( T && v ) {
f( std::make_pair( std::forward< T >( v ), 99 ) );
}

Done!

It’s inconsistent that the top of the paper uses typename std::remove_cv< typename std::remove_reference<T> but the bottom uses typename decay<T>::type.



2. The name “decay_copy” is a bit cryptic.

A. Most users are unfamiliar with the term “decay.” It’s an operation on C++ types rooted in C compatibility.

B. Decay, applied to reference type, emulates lvalue-to-rvalue conversion. But the point is to encapsulate that conversion, with no actual references entering into it. Calling this “decay” would perpetuate the myth that all lvalues are references “under the hood.”

C. Decay, applied to other types such as C arrays, isn’t useful here. Should an array be decayed or copied? The name is contradictory. (Neither should happen to an array rvalue; it should be passed as-is.)

D. decay_copy doesn’t copy unless it gets an lvalue. A proper implementation shouldn’t even require movability. “Copy” doesn’t belong in the name.

This is more than a bikeshed argument. The name is important because it should capture the purpose. It should look out-of-place in use-cases which are abuse. I’d suggest own_value, because it treats its argument as a value and returns an expression capturing ownership by rvalue-ness.



3. It’s almost never the right solution — including current usage in the Standard.

decay_copy indicates that new, temporary storage is required to back a reference parameter, and that’s seldom the fundamental intent. Usually, some other case applies:

A. Value semantics apply. The receiver of the value should be declared auto. This is the case in std::thread::thread and std::async: parameters are copied to member variables. The DECAY_COPY operation is only a fictional approximation. In particular, for by-value parameters, the specification in terms of DECAY_COPY doesn’t account for the second of two copy/moves necessary: from the calling thread argument to the persistent object, and from the persistent object to the called thread. This looks like a library defect.

The thread library specification is a bit exotic; what about real-world use? Normally the user generically obtains an owned value using a parameter of non-reference type, an auto local declaration, or a nonstatic member declared through std::decay_t. The last case is the trickiest, but it’s not amenable to decay_copy because nonstatic members can’t be declared auto. Using decay_t + decay_copy introduces an unnecessary move (potentially elided) from the stack into the class.

B. It’s ordinary parameter passing, and the choice is really between std::forward and std::move. Adding decay_copy to the mix would only confuse matters.

C. Something fishy is going on. The standard should not provide utilities to help with these cases. In Vlad’s example, a copy is made and immediately treated as const. Why? Is its address significant? Is a mutable member interfering in behavior?

D. A prvalue copy is specifically needed. A completely generic decay_copy is overkill. This was the case when I used it, to emulate pass-by-value using references:

void f( foo const & a )
{ f( decay_copy( a ) ); }

void f( foo && a ) {
// implementation
}

For this usage, the better syntax is f( foo{ a } ), and that’s what I replaced it with. The pattern is tricky if foo is a generic typename T — see my earlier mistake. However, this doesn’t happen generically. f’s implementation is using the copy as scratch space, which implies knowledge of the members of foo. Making a scratch copy should happen immediately before entering type-specific implementation.

For function templates containing implementation over several types, usually there’s SFINAE that already needs a decay_t<T> somewhere, to get the class type behind the reference. Adding another decay_t<T> in the body is idiomatic.

In any case, for the language to require boilerplate overloads, and provide decay_copy as their dedicated helper, seems like the wrong solution. The core language needs a “pass by value or reference” construct. There’s already EWG 26 <http://wg21.cmeerw.net/ewg/issue26> and the proposed <http://open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3538.html> T | syntax for “value or const reference,” perhaps this can be extended to “value or rvalue reference” spelled T ||. There’s a spectrum of possible approaches and related extensions that could solve the problem, without resorting to adding a new edge-case sibling to move and forward. And N3255’s decay_copy, which never returns by reference, would only be shorthand for std::decay_t< T > while still requiring the user to write std::forward< T >( a ).



TL;DR: I’ve encountered this problem a few times
 but I’ve encountered a lot of weird things relating to rvalues. decay_copy is “code smelly” enough that it shouldn’t be the path of least resistance.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Myriachan
2015-07-06 23:34:15 UTC
Permalink
Post by Vlad from Moscow
MS VC++ has the following bug shown in this demonstrative program
#include <iostream>
int main()
{
int x;
int{ x } = 10;
std::cout << "x = " << x << std::endl;
}
The expression with explicit type conversion using functional notation
int{ x } does not create a temporary object. The compiler compiles the code
and the program outputs 10.
Maybe so, but it would be better demonstrated without the UB. Why not give
x an initializer at its definition?
Beyond that, this isn't an MSVC compiler bug; it's a feature, to use the
cliché. Visual C++ allows explicit casts on rvalues to make them lvalues
like that, with a level-4 warning (C4213):
https://msdn.microsoft.com/en-us/library/yshyhfby.aspx

In Visual C++, the behavior is defined: you're casting x to its correct
type then assigning it, so you're assigning a value to a
previously-uninitialized variable. It's defined behavior, with Visual
C++'s definition of that lvalue cast.

(I've personally always thought that C++ ought to allow things like int() =
4;, which would do nothing since you're writing to a temporary that ceases
to exist soon after. But I'm kind of radical, so maybe it's best to ignore
me =^-^=)

Melissa
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Krauss
2015-07-07 01:18:37 UTC
Permalink
Post by Myriachan
https://msdn.microsoft.com/en-us/library/yshyhfby.aspx
In Visual C++, the behavior is defined: you're casting x to its correct type then assigning it, so you're assigning a value to a previously-uninitialized variable. It's defined behavior, with Visual C++'s definition of that lvalue cast.
Does it work like that inside all subexpressions or only on the LHS of an assignment expression? (Full-expression?)

Either way, I’ll just go ahead and forget all about this for any future discussions or proposals where this effective copy-elision could be dangerous. MS decides their own path.

Strangely, the example in the documentation doesn’t appear to use the described feature at all. It treats the result of the cast as an rvalue. The nonconforming part is that it treats the result of postfix ++ as an lvalue. If anything, that only demonstrates that assigning to the result of postfix ++ will override the increment operation.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2015-07-07 01:37:06 UTC
Permalink
Post by Myriachan
Beyond that, this isn't an MSVC compiler bug; it's a feature, to use the
cliché. Visual C++ allows explicit casts on rvalues to make them lvalues
https://msdn.microsoft.com/en-us/library/yshyhfby.aspx
In Visual C++, the behavior is defined: you're casting x to its correct
type then assigning it, so you're assigning a value to a
previously-uninitialized variable. It's defined behavior, with Visual
C++'s definition of that lvalue cast.
Does it work like that inside all subexpressions or only on the LHS of an
assignment expression? (Full-expression?)
Either way, I’ll just go ahead and forget all about this for any future
discussions or proposals where this effective copy-elision could be
dangerous. MS decides their own path.
Strangely, the example in the documentation doesn’t appear to use the
described feature at all. It treats the result of the cast as an rvalue.
The nonconforming part is that it treats the result of postfix ++ as an
lvalue. If anything, that only demonstrates that assigning to the result of
postfix ++ will override the increment operation.
The parens obscure what the example is doing. It's this part that uses the
extension:

(( int * )a )++

The operand to the postfix ++ is not an lvalue here, but VC++ allows it to
be incremented anyway.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Continue reading on narkive:
Loading...