Is there a technical reason why range-based for loop doesn't detect whether it's looping on an rvalue?

300 Views Asked by At

The reason for the question is that I've seen code like this:

auto fun(std::vector<Foo>&& v) {
    std::vector<Bar> w;
    for (auto&& e : v /* not an rvalue, but keep reading */) {
        w.push_back(std::move(e));
    }
    // do stuff with w
}

which is marked as erroneous by static analisys tools, as the forwarding reference e is being std::moved instead of being std::forwarded.

On the other hand, v binds for sure to a prvalue or an xvalue (something the client knows to be or wants fun to treat as a temporary), because its type is an rvalue reference. Yes, I see that the body of the function doesn't state in any way that v cannot be used after the for loop, but that would only lead me to think that I should change

  • for (auto&& e : v) to for (auto&& e : std::move(v)),
  • and auto&& to E&&, assuming something along the lines of using E = std::decay_t<decltype(v)>::value_type;.

As far as I've understood, the first point doesn't have the effect I would have expected. In fact, std::move seems to have no effect as far as the for is concerned. In turn, e keeps being initialized from an lvalue (at least if the frequent case that operator[] returns a reference for the type of v), and the second point simply causes a compilation error.

As an additional reference, the note ¹ from this answer reads (with reference to range-for loops)

You cannot detect if you are iterating over a temporary (or other rvalue)

which seems to confirm that I just can't do that.

But looking at how a range-for loop is desugared, what would be wrong in changing range-declaration = *__begin; to range-declaration = std::move(*__begin); when range-expression is an rvalue?

1

There are 1 best solutions below

6
On BEST ANSWER

the container is rvalue doesn't means what it contains is

// note: in real code this could be T&& and have no idea it's a std::span
void foo(std::span<X>&& s){
    // explicitly std::move should be required
    // because the code may want to use `s` later
    for(auto&& v : std::move(s)){
        // if the for loop forward the value category of <range-expression> to items
        // then decltype(v) is X&&, and this wrongly move-out the data
        X t = std::forward<decltype(v)>(v); 
    }
}

int main(){
    X x[2];
    foo(x);
}

the decision should be done by


* the container/adaptor should always return move_iterator, overload for rvalue begin/end would not work in current standard, the normal one is always called. Plus no standard container support them either.