问题
Calling the member function .begin()
of std::vector
and std::begin()
on rvalues result in different outputs, as the following test shows:
vector<int> a{ 1, 2, 3 };
vector<int>::iterator it1 = move(a).begin(); // OK
vector<int>::const_iterator it2 = move(a).begin(); // OK
vector<int>::iterator it3 = begin(move(a)); // Error!
vector<int>::const_iterator it4 = begin(move(a)); // OK
Here is my understanding: std::begin()
calls const&
overload (since it lack &&
overload), and therefore, it return a const_iterator
object. So, the returned value can be assigned to const_iterator
but not iterator.
- Is my understanding correct?
- Why does
std::begin()
not have an rvalue overload?
Just a note that I used move(a)
to demonstrate calling .begin()
and std::begin()
on rvalues. Of course, it can be replaced by any rvalue object for which .begin()
and std::begin()
are well defined.
Edit: Here is the real example showing where I encountered this issue. I've simplified a lot just to convey the idea where std::begin()
is called on an rvalue. So, since row_matrix
is a proxy class, there shouldn't be any problem calling begin
and end
on rvalues since the underlying object is identical.
class matrix_row;
class row_iterator;
class matrix {
public:
matrix_row row(int i);
// other members
};
class matrix_row { // <- proxy class representing a row of matrix
public:
row_iterator begin();
row_iterator end();
// other members
private:
int row_;
matrix& matrix_;
};
class row_iterator {
// defined everything needed to qualify as a valid iterator
};
matrix m(3,4);
for(auto x = m.row(1).begin(); x != m.row(1).end(); ++x) {
*x /=2; // OK
}
for(auto x = begin(m.row(1)); x != end(m.row(1)); ++x) {
*x /= 2; // Error
}
回答1:
Until recently, overloading .begin()
by rvalue/lvalue-ness of the invoking object was not possible.
When it was added, retrofitting such changes into the standard library could, in theory, break existing code.
Breaking existing code is bad, bad enough that legacy quirks are left in barring reasonably strong evidence that such code does not exist, that there would be clear diagnostics, and/or the effect of the change is really useful.
So .begin()
ignores rvalueness of its *this
.
There is no such restriction on std::begin
, other than possibly wishing compatibility with .begin()
.
In theory, standard containers don't have a proper respose to being called with std::begin
in an rvalue context. The "proper" way of interacting with std::move
or rvalues is that you aren't supposed to care about the state of the moved-from object after the call completes.
This means (logically) you can only get one of the two iterators out (begin or end).
What the proper semantics are in this case is a big puzzling. I have written adaptors that, in this situation (a pseudo-begin/end call on an rvalue) generate move iterators (for example), but doing that in general is very surprising, and I think it is a bad move in the end.
回答2:
23.2.1/12 states:
Unless otherwise specified (either explicitly or by defining a function in terms of other functions), invoking a container member function or passing a container as an argument to a library function shall not invalidate iterators to, or change the values of, objects within that container.
DR 2321 also wants to make it explicit:
no move constructor (or move assignment operator when
allocator_traits<allocator_type>::propagate_on_container_move_assignment::value
is true) of a container (except forarray
) invalidates any references, pointers, or iterators referring to the elements of the source container. [Note: Theend()
iterator does not refer to any element, so it may be invalidated. — end note]
Of course this would only be useful if you were moving into a different container, but you're not.
来源:https://stackoverflow.com/questions/37262329/member-function-begin-and-stdbegin