4

Consider this code:

template<typename T>
T mov(T&& t){
   return std::move(t);
}

int main(){
   std::unique_ptr<int> a = std::unique_ptr<int>(new int());
   std::unique_ptr<int> b = mov(a);
}

The mov function should simply take a universal reference and return it by value but by moveing it instead of copying. Thus, no copying should be involved when calling this method. Consequently, it should be fine to call such a function with a unique_ptr which can only be moved. However, this code does not compile: I receive the error:

test.cpp:24:34: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]’
    std::unique_ptr<int> b = mov(a);

So it seems that C++ tries to invoke unique_ptrs copy constructor which is of course deleted. But why does a copy happen here? How can I get this code to compile?

Niall
  • 30,036
  • 10
  • 99
  • 142
gexicide
  • 38,535
  • 21
  • 92
  • 152
  • 1
    That `std::unique_ptr b = mov(std::move(a));` *works* should be a hint to what is wrong. – WhozCraig Jul 22 '14 at 10:28
  • @WhozCraig: I especially want to avoid the `move` here. Otherwise, I would not need a universal reference but an rvalue reference. The point of a universal refence **is** that it can also hold a usual lvalue reference. – gexicide Jul 22 '14 at 10:29
  • Then I'm not seeing the end-game. What do you think is in `a` while within `mov` *without* moving per my comment? The `T&&` isn't promoted to an [xvalue](http://en.cppreference.com/w/cpp/language/value_category) as you've written it. It needs to be to be moved (or I am just not seeing it, which wouldn't be the first time). – WhozCraig Jul 22 '14 at 10:31
  • @WhozCraig: If I call `mov` with an rvalue, then `a` is `int&&` inside `mov`, if I call `mov` with an lvalue, then `a` is `int&` inside `mov`. [Here](http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers) is an explanation of the behviour. – gexicide Jul 22 '14 at 10:34
  • *Exactly*. So how do you expect passing simply `a` will deduce to an xvalue heading into `mov` ? Last I checked only `prvalue` or `xvalue` are movable, and `mov(a)` exhibits *neither* of those. – WhozCraig Jul 22 '14 at 10:41
  • @WhozCraig: I somehow managed to solve the problem. Check out my answer. – gexicide Jul 22 '14 at 10:43
  • 1
    That would do it. And not surprisingly, is nearly identical to `std::move` on my implementation (clang 3.4). gratz! – WhozCraig Jul 22 '14 at 10:48
  • @WhozCraig: Yeah, it is semantically identical, but you save the `std::move` on every function call. In a scenario where you are always handling `unique_ptrs` and it is clear that every function consumes them, adding allways `move` to every call is cumbersome and can be avoided with this trick. – gexicide Jul 22 '14 at 10:50
  • 1
    Ok I buy that. was trying to understand what the possible use case was until that comment. thanks!. – WhozCraig Jul 22 '14 at 10:51

2 Answers2

3

I have finally found a working solution. I think the problem is the return by value which will trigger a copy. Instead I need a return by rvalue reference; then a move will be conducted automatically. First I tried this:

template<typename T>
T&& mov(T&& t){
   return std::move(t);
}

But now, the problem is that the return type T&& is a universal reference, not an rvalue reference. Thus, when calling the function with an lvalue, the actual signature is T& mov(T& t). Because of that, its body will not compile because I cannot std::move to an lvalue reference. This was exactly what happend, here is the error:

test.cpp:18:22: error: invalid initialization of non-const reference of type 

‘std::unique_ptr<int>&’ from an rvalue of type ‘std::remove_reference<std::unique_ptr<int>&>::type {aka std::unique_ptr<int>}’
    return std::move(t);

So, I needed a real rvalue reference as return type. At first, I didn't know how to construct it but finally, I figured out is that I first need to std::remove_reference the type T and then add &&, then I will have a real rvalue reference T&&. And it worked, this version of mov compiles fine and solves the problem:

template<typename T>
typename std::remove_reference<T>::type&& mov(T&& t){
   return std::move(t);
}

As Niall stated, returning by reference also works by using remove_reference without &&:

template<typename T>
typename std::remove_reference<T>::type mov(T&& t){
   return std::move(t);
}
gexicide
  • 38,535
  • 21
  • 92
  • 152
  • Would making the return type `typename std::remove_reference::type` also work? – user541686 Jul 22 '14 at 10:48
  • @Mehrdad: Yes, it does. But I rly dunno why – gexicide Jul 22 '14 at 10:51
  • @Mehrdad Just saw your comment, sync typing... the return-by-value here was hinted in the original question. – Niall Jul 22 '14 at 10:51
  • @gexicide: It works because the move construction happens inside the function rather than outside, and then the resulting move from the rvalue output is either elided or re-moved into the outer variable. – user541686 Jul 22 '14 at 10:55
3

I believe the error is in the return type of mov

The code should read

#include <utility>
#include <memory>
template<typename T>
typename std::remove_reference<T>::type&& mov(T&& t){
   return std::move(t);
}

int main(){
   std::unique_ptr<int> a = std::unique_ptr<int>(new int());
   auto b = mov(a);
}

The question hinted at a return by value scenario, this also compiles. I'm not sure if it will work in your situation;

template<typename T>
typename std::remove_reference<T>::type mov(T&& t){
   return std::move(t);
}
Niall
  • 30,036
  • 10
  • 99
  • 142
  • I figured out the same right before you, but thanks for your answer anyway :) – gexicide Jul 22 '14 at 10:44
  • The solution was obvious to me, what's not obvious to me is the explanation for why the original is wrong. – user541686 Jul 22 '14 at 10:45
  • @gexicide I see it now. Synchronous typing. Nice little problem thought. I think the reference collapsing is going to get people for some time. It's bitten me a few times already. – Niall Jul 22 '14 at 10:46
  • @Niall: Yes, it is indeed quite vexing. I thought I had understood all that rvalue/move semantics stuff, but there are hidden problems lurking everywhere... – gexicide Jul 22 '14 at 10:47
  • @gexicide If the return-by-value was what was required, `typename std::remove_reference::type mov(T&& t)` also seems to compile. – Niall Jul 22 '14 at 10:50
  • @Niall: Why does that work? Shouldn't the type of `std::remove_reference::type` be simply `T` and thus identical to the version in my question? – gexicide Jul 22 '14 at 10:52
  • @gexicide. I reason, it returns an unnamed value, an rvalue. The original was templated to `T` which is deduced to be `T&`, the `std::remove_ref` strips the deduced `&` and you are left with `T` – Niall Jul 22 '14 at 10:54