Here is a possible implementation of std::move(). It's not fully conforming to the details of the standard, but it's very close:
template<class T>
typename std::remove_reference<T>::type&&
myMove( T&& Arg )
{
return ( ( typename std::remove_reference<T>::type&& )Arg );
}
I don't understand why it wouldn't work if we replace typename std::remove_reference<T>::type&& by the T&&, i.e.
template<class T>
typename std::remove_reference<T>::type&&
myMove( T&& Arg )
{
return ( (T&&) Arg );
}
The issue is not really about the move, but the reference collapsing that happens with T&&.
The are nice write-ups about this here and these Q&A here, and here.
I'll focus on the cast (T&&) and why that doesn't work.
Given the reference collapsing rules listed above;
T& & becomes T&
T& && becomes T&
T&& & becomes T&
T&& && becomes T&&
The problem arises when T is deduced to be an lvalue reference, then T&& becomes T& && which collapses to T&, given the cast, you merely cast it back to an lvalue reference and you have not gained the rvalue reference that enables moving to happen.
The std::remove_reference<T>::type is there to remove all the references and the && then adds back the rvalue ref and thus ensures the correct cast is made.
The typename is there to disambiguate the member type over a member variable.
Related
Given the following reference collapsing rules
T& & --> T&
T&& & --> T&
T& && --> T&
T&& && --> T&&
The third and fourth rule imply that T(ref qualifer) && is the identity transformation, i.e. T& stays at T& and T&& stays at T&&. Why do we have two overloads for std::forward? Couldn't the following definition serve all purposes?
template <typename T, typename = std::enable_if_t<!std::is_const<T>::value>>
T&& forward(const typename std::remove_reference<T>::type& val) {
return static_cast<T&&>(const_cast<T&&>(val));
}
Here the only purpose the const std::remove_reference<T>& serves is to not make copies. And the enable_if helps ensure that the function is only called on non const values. I'm not entirely sure whether the const_cast is needed since it's not the reference itself that's const.
Since forward is always called with explicit template parameters there are two cases we need to consider:
forward<Type&>(val) Here the type of T in forward will be T& and therefore the return type will be the identity transformation to T&
forward<Type&&>(val) Here the type of T in forward will be T&& and therefore the return type will be the identity transformation to T&&
So then why do we need two overloads as described in http://en.cppreference.com/w/cpp/utility/forward?
Note: I am not sure if std::forward is ever used with const types, but I disabled forward in that case, because I have never seen it used like that. Also move semantics don't really make sense in that case either.
A good place to start would be Howard Hinnant's answer and paper on std::forward().
Your implementation handles all the normal use-cases correctly (T& --> T&, T const& --> T const&, and T&& --> T&&). What it fails to handle are common and easy-to-make errors, errors which would be very difficult to debug in your implementation but fail to compile with std::forward().
Given these definitions:
struct Object { };
template <typename T, typename = std::enable_if_t<!std::is_const<T>::value>>
T&& my_forward(const typename std::remove_reference<T>::type& val) {
return static_cast<T&&>(const_cast<T&&>(val));
}
template <class T>
void foo(T&& ) { }
I can pass non-const references to const objects, both of the lvalue variety:
const Object o{};
foo(my_forward<Object&>(o)); // ok?? calls foo<Object&>
foo(std::forward<Object&>(o)); // error
and the rvalue variety:
const Object o{};
foo(my_forward<Object>(o)); // ok?? calls foo<Object>
foo(std::forward<Object>(o)); // error
I can pass lvalue references to rvalues:
foo(my_forward<Object&>(Object{})); // ok?? calls foo<Object&>
foo(std::forward<Object&>(Object{})); // error
The first two cases lead to potentially modifying objects that were intended to be const (which could be UB if they were constructed const), the last case is passing a dangling lvalue reference.
I've stumbled upon Scott Mayers article on universal references, link.
From what I understood universal reference, that is some type T&& can mean an rvalue or lvalue type in different contexts.
For example:
template<typename T>
void f(T&& param); // deduced parameter type ⇒ type deduction;
// && ≡ universal reference
In the above example depending on template parameter the T&& can be either an lvalue or rvalue, that is, it depends on how we call f
int x = 10;
f(x); // T&& is lvalue (reference)
f(10); // T&& is rvalue
However according to Scott, if we apply const to to above example the type T&& is always an rvalue:
template<typename T>
void f(const T&& param); // “&&” means rvalue reference
Quote from the article:
Even the simple addition of a const qualifier is enough to disable the
interpretation of “&&” as a universal reference:
Question:
Why does const make an "universal" reference rvalue?
I think this is impossible because following code makes confusion then:
template<typename T>
void f(const T&& param); // “&&” means rvalue reference
int x = 10;
f(x); // T&& is lvalue (reference) // how does 'x' suddenly become an rvalue because of const?
f(10); // T&& is rvalue // OK
This question already has answers here:
Why is template argument deduction disabled with std::forward?
(3 answers)
Closed 8 years ago.
Here is code below. Why if I replace typename remove_reference<S>::type& with S& it won't work nicely? (I mean a type will be deduced wrong)
If I pass an rvalue (let int be int), then S will be deduced as int, a's type is int&, forward() returns int&& (rvalue).
If I pass an lvalue (int) S will be deduced as int&, a's type is int& & ->int&, forward() returns int& &&->int&. everything works well, so why do we need that remove_reference?
template<class S>
S&& forward(typename remove_reference<S>::type& a) noexcept
{
return static_cast<S&&>(a);
}
Consider the original use case of forward:
template<class T>
void f(T&& t) { g(std::forward<T>(t)); }
t has a name, so it's an lvalue inside f even if it's bound to an rvalue. If forward is allowed to deduce type, then people would be tempted to write std::forward(t) and not actually get the perfect forwarding they expected.
Also, your analysis is not right. template<class S> void f(S& t); doesn't bind to rvalues. std::forward is actually a pair of overloads - the one you are referring to takes lvalues only, and
template <class T> constexpr T&& forward(remove_reference_t<T>&& t) noexcept;
handles rvalues.
I'm looking at [VC10's] unique_ptr and they do a couple things I don't understand:
typedef typename tr1::remove_reference<_Dx>::type _Dx_noref;
_Dx_noref& get_deleter()
{ // return reference to deleter
return (_Mydel);
}
unique_ptr(pointer _Ptr,
typename _If<tr1::is_reference<_Dx>::value, _Dx,
const typename tr1::remove_reference<_Dx>::type&>::_Type _Dt)
: _Mybase(_Ptr, _Dt)
{ // construct with pointer and (maybe const) deleter&
}
typename tr1::add_reference<_Ty>::type operator*() const
{ // return reference to object
return (*this->_Myptr);
}
Wouldn't just writing _Dx& or _Ty& be the same thing?
I actually do understand why they did it here though:
unique_ptr(pointer _Ptr, typename tr1::remove_reference<_Dx>::type&& _Dt)
: _Mybase(_Ptr, _STD move(_Dt))
{ // construct by moving deleter
}
get_deleter
Any reference is removed from the return type, then a reference is added back. In conformant C++11, adding a & to an existing & (or &&) produces a &. In C++03 however, that would be forming a reference to reference type, which was illegal. Likely MSVC is using the old rules, or that code was written when it did and remains because it is harmless.
constructor
Here they remove the reference, add const, and then add the reference back, to be passing by const reference. This is because adding const directly to a reference type does nothing! (§8.3.2/1) In either C++11 or C++03, the parameter declaration would be valid but would not add a const, if the reference weren't removed and replaced.
operator*
This is essentially the same as get_deleter, but they went about it a different way, and _Ty cannot be a reference type to begin with. It looks to me like _Ty& would suffice, but it's their prerogative.
Here's an example, possibly archetypal, for why we need remove_reference, in the implementation of std::move: The goal is to return an rvalue-reference type, based on the deduced type of the function argument.
Example: Foo x; move(x); Here move(x) should return a type Foo&&. But the argument of move is an expression of type Foo&. So how can the move function deduce the right type?
The first attempt is to use ordinary template argument deduction and use a cast:
template <typename T> T && move(??? x) { return static_cast<T&&>(x); }
But what should go into ???? If we say T x, then T will be deduced as Foo&; if we say T & x, then T = Foo, and if we say T && x, it won't match at all. The second version, T & x, appears to be useful.
But then the function doesn't work on rvalues to begin with (e.g. move(Foo(...)). In this case, we want T && x so that T = Foo and T&& = Foo&& as desired. We could have two overloads, but having multiple overloads is undesirable because it increases complexity needlessly. And finally, if someone were to specify the template paramete explicitly as move<Foo&>(x), the function would never work, because when T = Foo&, then T&& = Foo& as well.
So in comes remove_reference:
template <typename T>
typename std::remove_reference<T>::type && move(T && x)
{
return static_cast<typename std::remove_reference<T>::type &&>(x);
}
First off, the new reference collapsing rules imply that T is deduces as either Foo& or Foo&& in the two cases. Then, remove_reference strips the reference and gives type Foo in any case, and adding && makes the desired Foo&& return type.
In an oversimplified summary: we need remove_reference because (Foo&)&& is Foo& and not Foo&&. If you ever write template code that needs the base type of a template paramter that could be deduced as either U& or U&&, you can use this model.
template class add_reference has specialization for void, const void and const volatile void because reference of void type (void&) is not allowed. If _Ty& is used, it would generate a compilation error when _Ty = void.
I got next snippet from microsoft
template <typename T> struct RemoveReference {
typedef T type;
};
template <typename T> struct RemoveReference<T&> {
typedef T type;
};
template <typename T> struct RemoveReference<T&&> {
typedef T type;
};
template <typename T> typename RemoveReference<T>::type&& Move(T&& t) {
return t;
}
...
remote_integer x = frumple(5);
remote_integer&& x1 = Move(x);
and i get an error "error C2440: 'return' : cannot convert from 'remote_integer' to 'remote_integer &&'"
something changed in compilers? With std::move all goes right.
The reason your Move doesn't work, is because t is always lvalue (even when T&& resolves to, say, int&&). Even though it might seem weird, named rvalue references are indeed lvalues.
When returning from your Move, you attempt to implicitly bind lvalue to rvalue reference, which is forbidden by standard (§8.5.3). As noted in the comments, you have to cast t explicitly to rvalue reference.
Relevant parts of standard are §5/4 and §5/5, but I'm going to quote note §5/6, which sums this nicely:
In general, the effect of this rule is that named rvalue references
are treated as lvalues and unnamed rvalue references to objects are
treated as xvalues; rvalue references to functions are treated as
lvalues whether named or not.
Correct implementation is indeed:
template <typename T>
typename std::remove_reference<T>::type&& move(T&& t)
{
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
As far as I remember, this code used to be valid in earlier drafts. But since the rules have changed, you have to provide explicit cast now (same applies to std::forward).