2

For regular local const reference variables, the scope is prolonged. Which is why the following code works as expected:

#include <iostream>
#include <memory>

struct foo
{
        foo()
        {
                std::cout << "foo() @" << (void*)this << std::endl;
        }
        ~foo()
        {
                std::cout << "~foo() @" << (void*)this << std::endl;
        }
};

int main()
{
        auto const& f = std::make_shared<foo>();
        std::cout << "f = " << f.get() << std::endl;
        return 0;
}

// prints:
// foo() @0x55f249c58e80
// f = 0x55f249c58e80
// ~foo() @0x55f249c58e80

It seems though that this does not work as expected when assigning a moved object using std::move():

#include <iostream>
#include <memory>
#include <list>

struct foo
{
        foo()
        {
                std::cout << "foo() @" << (void*)this << std::endl;
        }
        ~foo()
        {
                std::cout << "~foo() @" << (void*)this << std::endl;
        }
};

int main()
{
        std::list<std::shared_ptr<foo>> l;
        l.push_back(std::make_shared<foo>());
        auto const& f = std::move(l.front());
        l.clear();
        std::cout << "f = " << f.get() << std::endl;
        return 0;
}

// prints
// foo() @0x564edb58fe80
// ~foo() @0x564edb58fe80
// f = 0x564edb58fe80

Does std::move() indeed change the scope, or am I dealing with a compiler bug?

Changing the variable from auto const& f to just auto f fixes the problem. If I wrap the move into another function, it also works:

auto const& f = [&]() { return std::move(l.front()); }();

It's almost like std::move() does not share the same semantics as a function call, but rather as if it was just a regular variable assignment:

auto const& f = std::move(l.front());
Tom
  • 653
  • 1
  • 8
  • 15
  • You misunderstand `std::move` but I'm not sure how exactly. `std::move` has no effect in any of the examples you've shown, except maybe to disable some optimizations. `std::move` is just a cast from `T&` to `T&&`, it converts lvalue references to rvalue references. – François Andrieux Oct 03 '18 at 19:31
  • "when assigning a moved object using std::move()" looks like it is necessary to repeat to some people again and again: `std::move()` itself does not move anything – Slava Oct 03 '18 at 19:33
  • @FrançoisAndrieux it does affect t see my answer – Slava Oct 03 '18 at 19:39
  • I realize it doesn't have any effect in this particular sample. I was actually moving an object from a `std::list` element into a local const reference variable, then deleting the entry from the list, and expected the variable to still be valid. – Tom Oct 03 '18 at 19:41
  • I updated the second snippet in my question with what I actually encountered. Moving an element out of a `std::list` using `std::move()`. Changing `auto const& f` to `auto f` fixes it, but at the same time I would have expected the same behavior. – Tom Oct 03 '18 at 19:50
  • @NathanOliver: This is not a duplicate of a version-specific question concerning C++03, because `std::move` didn't exist in C++03. It probably still is a duplicate, just that target doesn't work. – Ben Voigt Oct 03 '18 at 19:52
  • @BenVoigt `std::move` is doing the exact same thing. Do we really need a pre and post C++11 dupe for passing the temporary to a function by reference stops the lifetime extension? – NathanOliver Oct 03 '18 at 19:53
  • Related, but also pre-rvalue references: https://stackoverflow.com/q/14735630/103167 – Ben Voigt Oct 03 '18 at 19:54
  • @NathanOliver: Yes, because the rule changed dramatically, you need a question about the C++11-and-later rule. – Ben Voigt Oct 03 '18 at 19:54
  • Well, we both get a vote. I for one don'y think it has changed, at least not for this scenario. – NathanOliver Oct 03 '18 at 19:56
  • @BenVoigt Did you note that the answer in the linked to dupe is referencing the C++11 standard when they quote *A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.* – NathanOliver Oct 03 '18 at 19:58
  • @Tom it is not possible to "move an element into a const reference variable". A reference variable refers to some other object, it doesn't contain an object – M.M Oct 03 '18 at 20:08

1 Answers1

-1

Let's put aside std::move() and create this function:

struct sometype {};

const sometype &foobar( const sometype &cref ) { return cref; }

and now we use it:

const sometype &ref = foobar( sometype() );

What do you think, will lifetime of temporary be prolongated in this case? No it would not. Lifetime is only prolongated when you assign to a reference directly. When it goes through a function or through static_cast or std::move that prolongation is gone. So you have exactly the same issue with std::move()

struct sometype {
    sometype() { std::cout << "ctype()" << std::endl; }
    ~sometype() { std::cout << "~ctype()" << std::endl; }
};

const sometype &foobar( const sometype &cref ) { return cref; }

int main()
{
    const sometype &cref1 = foobar( sometype() );
    sometype &&cref2 = std::move( sometype() );
    std::cout << "main continues" << std::endl;
}

output:

ctype()
~ctype()
ctype()
~ctype()
main continues     

live example

Note: you should not use std::move() on return statement, it does not give you anything.

For your code change. You should remember that std::move() does not move anything. It is done by special assignment operator or constructor (if they provided for a type). So when you write this code:

const type &ref = std::move( something );

there is no constructor nor assignment operator involved and so no moving happens. For actual moving to happen you have to assign it to a variable:

type val = std::move( something );

now moving would happen if possible or copy otherwise.

Slava
  • 43,454
  • 1
  • 47
  • 90
  • 1
    This really doesn't answer the question. It just shows a better(?) mcve – NathanOliver Oct 03 '18 at 19:43
  • That note is not true 100% of the time. Sometimes, `std::move` is required to avoid a copy. – Rakete1111 Oct 03 '18 at 19:46
  • @Rakete1111 I did not say it is because of a copy, does not matter if copy or not lifetime prolongation is gone, as temporary is an argument for a function and is not directly assigned to a reference. See my example – Slava Oct 03 '18 at 19:50
  • I know that `std::move()` doesn't really do anything in the example. I updated the second example to what I encountered: moving an element out of a `std::list` into a local const reference variable, then deleting that element, and then using the local variable (expecting it to still be valid). – Tom Oct 03 '18 at 19:54
  • @Tom I already answered, your temporary is passed to `std::move()` not to reference, so lifetime prolongation is gone. Your new code has different issue - you have dangling reference, you should not jump from code t code like that. This is 2 different cases. – Slava Oct 03 '18 at 19:59
  • @Tom: Your code does not "move an element out of a `std::list`", it refers to it in-place. The `auto f = ...` variant does actually move the content. – Ben Voigt Oct 03 '18 at 19:59
  • @Slava: What temporary? We're talking about this line of code, `auto const& f = std::move(l.front());` right? The only instance of the object lives inside a `std::list` container. When it is removed from there, all references and pointers to the object become invalid. – Ben Voigt Oct 03 '18 at 20:02
  • @BenVoigt there was temporary - result of `std::make_shared<>` before OP changed his code. – Slava Oct 03 '18 at 20:03
  • I changed the code because some answers pointed out that `std::move` around a function return value is pointless. Which it is. – Tom Oct 03 '18 at 20:04
  • @Tom "I changed the code because some answers pointed out that std::move around a function return value is pointless." you got it wrong. Issue is prolongation does not work because of `std::move()` existance in your original code. – Slava Oct 03 '18 at 20:05
  • @Slava, I think I agree with you, that's certainly how it behaves. But because syntactically `std::move()` appears to be like a function call, I expected lifetime to be prolonged. It makes sense that it doesn't, after thinking about it. – Tom Oct 03 '18 at 20:06
  • @Tom tha's why I showed you that with a function call it does not work either, though some purists do not like my example. – Slava Oct 03 '18 at 20:07
  • @Slava: Your example is fine (and I didn't downvote), but I think the complaint is that it is a restatement of the question. – Ben Voigt Oct 03 '18 at 20:09
  • @Tom: `std::move(x)` isn't just syntactically like a function call, it is a function call. – Ben Voigt Oct 03 '18 at 20:10
  • @BenVoigt I realize that now. I accepted this answer because it illustrates the exact same "problem". Thanks for helping me understand this. – Tom Oct 03 '18 at 20:11