0

I have a situation which I cannot wrap my head around. I define a non-copy-able struct and want to in-place construct it in a tuple. If I do so, it is copied. I thought the problem may be std::make_tuple, but it isn't.

If instead I construct the object, then move it in the tuple, things work as expected.

Demonstration (I use std::exit to prevent the output of normal destruction).

#include <tuple>
#include <iostream>
#include <cstdlib>

struct MoveMe {
    MoveMe(size_t s) : size(s) {}
    MoveMe(MoveMe&&) = default;
    MoveMe(const MoveMe&) = delete;

    MoveMe& operator=(MoveMe&&) = default;
    MoveMe& operator=(const MoveMe&) = delete;

    ~MoveMe() {
        std::cout << "Destroyed" << std::endl;
    }

    size_t size = 0;
};

int main(int, char**) {
    std::cout << "Constructed in-place." << std::endl;
    auto tuple = std::make_tuple(MoveMe{10});

    std::cout << std::endl << "Other tests." << std::endl;
    std::tuple<MoveMe> tuple2 = std::tuple<MoveMe>(MoveMe{10});

    std::cout << std::endl << "Moved." << std::endl;
    MoveMe obj{10};
    auto tuple3 = std::make_tuple(std::move(obj));

    std::exit(0);
    // return 0;
}

Outputs

Constructed in-place.

Destroyed

Other tests.

Destroyed

Moved.

Any ideas why that is? My understanding of rvalues is basic, so my guess is I'm missing something obvious. Thank you.

scx
  • 3,221
  • 1
  • 19
  • 37
  • What do you mean by it is copied? – user2296177 Apr 17 '17 at 19:38
  • Well the destructor is called, so it is copied in the tuple. No? – scx Apr 17 '17 at 19:39
  • You seem to be confused due to the calls to `~MoveMe()`. When there is a move operation, the variable that was moved from still remains, it must still be destroyed; it might still contain some other data that must be properly released. You're creating a temporary and moving from it. That temporary will be destroyed right after the expression is done and its contents have been moved into the tuple. – user2296177 Apr 17 '17 at 19:41

1 Answers1

1

It isn't copied it is still moved.

In your case, you're creating a temporary (MoveMe{10}) that is then used to move construct the instance of MoveMe in the tuple. After the instance of MoveMe in the tuple is move constructed, the temporary that was moved is destroyed.

You can directly construct the MoveMe object in the tuple by forwarding the arguments

std::cout << "Directly Constructed" << std::endl;
auto tuple = std::tuple<MoveMe>(10);

Which will not result in a temporary being created then destroyed.

lcs
  • 4,227
  • 17
  • 36
  • Ok, so as stated in the comments, I am definitely confused :) Doesn't creating the object directly return an rvalue? – scx Apr 17 '17 at 19:44
  • It is an rvalue, but still a valid object. When you move construct you need two instances of your object. The object you're moving from and the object you're moving too. In your case the object you're moving from (`MoveMe{10}`) is a temporary rvalue that will be destructed as soon as control leaves scope (the statement it is defined in). – lcs Apr 17 '17 at 19:47
  • Wow, well that's a "mind blown" moment. I guess I have to study some more basics. Thank you for explaining the issue. – scx Apr 17 '17 at 19:49