Question Details

No question body available.

Tags

c++ rvalue-reference perfect-forwarding

Answers (2)

Accepted Answer Available
Accepted Answer
January 13, 2026 Score: 6 Rep: 280,126 Quality: Expert Completeness: 60%

When you are accessing a member of a structure, if the structure is an rvalue the member is accessed with that in mind.

struct Bob {
  std::vector data1;
  std::set data2;
};

so if we do:

Bob bob;

drawStuff( std::move(bob).data1 );

this gives you an rvalue vector. Despite having moved bob, we only access the data1 member; data2 is not touched, nor anything else in bob.

One must remember that in C++, std::move does not move, it just marks something as "this is permitted to be moved from" for other code to move from it. So a statement consisting of std::move(x); and nothing else is a noop.

tuple takes this same pattern and applies it to its content; a std::get( std::move(tup) ) is like doing a "member access" for "member zero" of the tuple.

Now, the question becomes, why not do std::move( std::get(tup) )? Well, the two are different in both the tuple and structure case.

struct Alice {
  std::vector& ref;
  std::set data;
};

here we have the (honestly pathological) Alice structure. If you do:

std::vector vec{1,2,3};
Alice alice{vec}; // have to initialize the ref member

drawStuff( std::move(alice).ref );

it will not move from the ref member; the fact that the structure containing the reference is being thrown away shortly doesn't mean that the referred to data is equally safe to dispose of and reuse.

The same follows for a tuple; a tuple with an lvalue reference, when the tuple is moved, doesn't make the reference an rvalue reference.

In your specific case, std::forward is being used instead of std::move, but std::forward is just a conditional std::move.

Tuples should only be used in generic code, when you have a bundle of data you need to treat uniformly despite its type but also need to carry around between spots. And the forward semantics of tuple are useful here, but not always.

As an example, suppose we want to write something like std::bind or std::async. We'd end up probably making a tuple of std::decay'd copies of the data, forwarded into the tuple! Then, depending on if the call was in an rvalue-like context or not, we'd unbundle it as a move on the tuple or not. At the same time, we might want to be std::ref aware and unbox any of them at the point of use.

At some point you have to be aware that std::move is not a move, just a cast giving other code permission, and that std::forward is a conditional move, and that the cargo-cult patterns you use for most code don't cover it when doing more advanced forwarding and you have to learn what the actual operations guarantees are.

std::get on an rvalue std::tuple is guaranteed not to mess with other elements of the tuple, so it is safe to cast a tuple to an rvalue and call std::get on it. The resulting semantics of get act like member-access on an rvalue or lvalue. You probably need to think about if this lines up with exactly what you want, and document what you are doing; but the chosen semantics where not chosen randomly, so sometimes it does do what you need.

Using cargo-cult like rules of thumb on move is generally a good idea in most code; having to reason about this every time is a real pain. So for the most part, your rule of thumb (don't touch moved-from data after a move) is exactly what you want to follow, and it makes the reader of your code feel happy as well. In this case, you both should think about it and you should document it to ensure that the reader of your code knows what you intended and that it works.

January 13, 2026 Score: 4 Rep: 66,171 Quality: Medium Completeness: 50%

Not in general

std::moveing (or std::forwarding) a value twice is immediately suspicious. The linked answer is correct in the general case.

In the case of unpacking a tuple-like value, we can reason it out as ok.

Because we know std::get is overloaded to preserve value category, and only operates on one member of the tuple at a time, the suspicious multiple move can easily be reasoned as safe.

However you must be vigilant that each member is only touched once.

template 
void func(Tuple && tuple, std::indexsequence) {
    (f(std::get(std::forward(tuple))), ...);
}

int main() { func(std::make
tuple(std::string(), std::string()), std::index_sequence{}); // Bad! }