Member function .begin() and std::begin()

隐身守侯 提交于 2019-12-04 01:50:54

问题


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.

  1. Is my understanding correct?
  2. 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 for array) invalidates any references, pointers, or iterators referring to the elements of the source container. [Note: The end() 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!