std::list<my_type> my_list;
std::list<my_type>::iterator my_iter = my_list.begin();
std::list<my_type>::iterator my_prev = std::prev(my_iter);
What is the value of my_prev?
Is it my_list.rend(), even though it is technically a different type?
How to check for such condition besides my_iter == my_list.begin()?
According to C++11 standard:
24.4.4 Iterator operations [iterator.operations]
§ 7
template <class BidirectionalIterator>
BidirectionalIterator prev(BidirectionalIterator x,
typename std::iterator_traits<BidirectionalIterator>::difference_type n = 1);
Effects: Equivalent to advance(x, -n); return x;
In the same section, but §2 and §3
template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n);
Requires: n shall be negative only for bidirectional and random access iterators.
Effects: Increments (or decrements for negative n) iterator reference i by n.
Then, for decrementing bidirectional iterators:
24.2.6 Bidirectional iterators
Expression Return type Operational Assertion/note
semantics pre-/post-condition
pre: there exists s such that
--r X& r == ++s.
post: r is dereferenceable.
--(++r) == r.
--r == --s implies r == s.
&r == &--r.
So, it looks like (or at least I couldn't find more relevant part from the standard), that this behavior is not defined.
I'd suggest you to maintain this situation and be careful with my_iter's value, before passing it to std::prev.
Related
I was told here that:
The order of generate is not guaranteed => depending on the implementation
I have looked up gcc's implementation of generate:
for (; __first != __last; ++__first)
*__first = __gen();
And Visual Studio implements it identically to that. This is a relief to me as using a lambda in generate that reads and writes to a capture could have undeterministic results:
int foo[] = {1, 0, 13};
vector<int> bar(3);
generate(bar.begin(), bar.end(), [&]() {
static auto i = 0;
static auto total = 0;
total += foo[i];
return foo[i] / total;
});
I expect bar to contain {1, 0, 0}.
If I am allowed to execute out of order this could even cause a divide by 0 error.
So I'd sleep easier if this question could be answered with proof that generate is required to execute sequentially.
As a note here, I know that experimental::parallel::generate will not be sequential. I'm just asking about generate.
I was intrigued by this so have done some research.
At the bottom of this answer is a copy of the relevant section from the standard as of 2011.
You will see from the template declaration of std::generate<> that the iterator parameters must conform to the concept of a ForwardIterator and OutputIterator respectively.
A ForwardIterator does not support random access. it can only read or write sequentially. An OutputIterator is even more restrictive - its operator* implicitly includes the effect of an operator++. This is an explicit feature of the concept.
Therefore, by implication, the implementation of this function must access elements sequentially (and therefore generate values sequentially) since to not do so would break the contract implicit in the interface.
Therefore the standard does (implicitly and quietly) guarantee that std::generate initialise its elements sequentially. It would be impossible to write a well-formed implementation of std::generate that did not.
QED
25.3.7 Generate [alg.generate]
template<class ForwardIterator, class Generator>
void generate(ForwardIterator first, ForwardIterator last,
Generator gen);
template<class OutputIterator, class Size, class Generator>
OutputIterator generate_n(OutputIterator first, Size n, Generator gen);
1 Effects: The first algorithm invokes the function object gen and
assigns the return value of gen through all the iterators in the range
[first,last). The second algorithm invokes the function object gen and
assigns the return value of gen through all the iterators in the range
[first,first + n) if n is positive, otherwise it does nothing.
2
Requires: gen takes no arguments, Size shall be convertible to an
integral type (4.7, 12.3).
3 Returns: generate_n returns first + n for
non-negative values of n and first for negative values.
4 Complexity:
Exactly last - first, n, or 0 invocations of gen and assignments,
respectively.
Here is all the standard says about it (25.7.3/1):
template<class ForwardIterator, class Generator>
void generate(ForwardIterator first, ForwardIterator last, Generator gen);
template<class OutputIterator, class Size, class Generator>
OutputIterator generate_n(OutputIterator first, Size n, Generator gen);
The first algorithm invokes the function object
gen
and assigns the return value of
gen
through
all the iterators in the range
[first,last)
. The second algorithm invokes the function object
gen
and assigns the return value of
gen
through all the iterators in the range
[first,first + n)
if
n
is
positive, otherwise it does nothing.
As you can see, it is not explicitly stated that the iterator assignments must be done sequentially.
ISO C++11 24.3:
template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n);
// ...
template <class ForwardIterator>
ForwardIterator next
(
ForwardIterator x,
typename std::iterator_traits<ForwardIterator>::difference_type n = 1
);
Why std::next does not accept InputIterators?
One of legal use cases I am thinking about is:
first = find(next(first, x), last, 11); // ...
I have found appropriate DR:
next/prev return an incremented iterator without changing the value of the original iterator. However, even this may invalidate an InputIterator. A ForwardIterator is required to guarantee the 'multipass' property.
But I don't understand how multipass/invalidation is related to that. Using same multipass/invalidation reasoning, we can even ban std::find for InputIterators:
template<class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& value);
There is nothing special about std::next in compare to std::find or std::vector::insert(pos, first, last) which have perfectly legal use cases for InputIterators
Moreover std::next(it, n) can be used in generic code, which operates not only on InputIterators.
In effect, input iterators cannot be usefully copied, because once an input iterator is incremented, any copy left lying around is invalidated.
std::next takes an iterator and returns another iterator which has been advanced n times. You can't do that with an input iterator without invalidating the original iterator, which makes std::next pointless. By constrast, std::advance advances the specified iterator n times, which is fine with an input iterator.
std::next is the iterator generalization of operator+(T*, size_t). std::advance is the iterator generalization of operator+=(T*&, size_t). It may well be that std::advance, like operator+=, should return a reference instead of void.
It's true that there is a similar issue with std::find (and related functions); they, too, will invalidate any copy of the specified input iterators. But it is quite possible that the committee found that issue less serious.
I'm writing a special iterator class that works like std::istream_iterator and many others by using a default-constructed instance to mark end of iteration. I want to give it the bidirectional iterator category. After running the following code:
MyIterType i_cur(get_some_iter()), i_end;
while(i_cur != i_end) ++i_cur;
do the standard requirements for bidirectional iterators impose the following to be valid?
--i_cur
++i_cur
--i_end or ++i_end
Thank you for quoting the standard if possible. I compile in C++03, but if C++11 introduces changes, I'm also interested to know them.
The Standard says that the answer to your question is yes:
[C++03] : 24.1.4 Bidirectional iterators
Table 75—Bidirectional iterator requirements (in addition to forward iterator)
expression return type operational assertion/note
semantics pre/post-coindition
======== ============ ============ ====================
--r X& pre: there exists s such
that r == ++s.
post: s is dereferenceable.
--(++r) == r.
--r == --s implies r
== s.
&r == &--r
In 24.2.6 in C++11, Table 110 gives the additional requirements for a bidirectional iterator.
In particular, if there exists s such that r == ++s, then --r must be valid and the resulting r must be dereferenceable. In addition:
--(++r) == r.
--r == --s implies r == s.
&r == &--r.
So unless the initial i_cur returned by get_some_iter is a past-the-end iterator, the final i_cur has a predecessor and so must be decrementable. The same holds for i_end, since it is the successor of the predecessor of the final i_cur.
Is there more beyond advance takes negative numbers?
std::advance
modifies its argument
returns nothing
works on input iterators or better (or bi-directional iterators if a negative distance is given)
std::next
leaves its argument unmodified
returns a copy of the argument, advanced by the specified amount
works on forward iterators or better (or bi-directional iterators if a negative distance is given))
Perhaps the biggest practical difference is that std::next() is only available from C++11.
std::next() will advance by one by default, whereas std::advance() requires a distance.
And then there are the return values:
std::advance(): (none) (the iterator passed in is modified)
std::next(): The n th successor.
std::next() takes negative numbers just like std::advance, and in that case requires that the iterator must be bidirectional. std::prev() would be more readable when the intent is specifically to move backwards.
std::advance
The function advance() increments the position of an iterator passed as the argument. Thus, the function lets the iterator step forward (or backward) more than one element:
#include <iterator>
void advance (InputIterator& pos, Dist n)
Lets the input iterator pos step n elements forward (or backward).
For bidirectional and random-access iterators, n may be negative to step backward.
Dist is a template type. Normally, it must be an integral type because operations such as <, ++, --, and comparisons with 0 are
called.
Note that advance() does not check whether it crosses the end() of a sequence (it can’t check because iterators in general do not know the
containers on which they operate). Thus, calling this function might
result in undefined behavior because calling operator ++ for the end
of a sequence is not defined.
std::next(and std::prev new in C++11)
#include <iterator>
ForwardIterator next (ForwardIterator pos)
ForwardIterator next (ForwardIterator pos, Dist n)
Yields the position the forward iterator pos would have if moved forward 1 or n positions.
For bidirectional and random-access iterators, n may be negative to yield previous ositions.
Dist is type std::iterator_traits::difference_type.
Calls advance(pos,n) for an internal temporary object.
Note that next() does not check whether it crosses the end() of a sequence. Thus, it is up to the caller to ensure that the result is
valid.
cite from The C++ Standard Library Second Edition
They're pretty much the same, except that std::next returns a copy and std::advance modifies its argument. Note that the standard requires std::next to behave like std::advance:
24.4.4 Iterator operations [iterator.operations]
template <class InputIterator, class Distance>
void advance(InputIterator& i [remark: reference], Distance n);
2. Requires: n shall be negative only for bidirectional and random access iterators
3. Effects: Increments (or decrements for negative n) iterator reference i by n.
[...]
template <class ForwardIterator>
ForwardIterator next(ForwardIterator x, [remark: copy]
typename std::iterator_traits<ForwardIterator>::difference_type n = 1);
6. Effects: Equivalent to advance(x, n); return x;
Note that both actually support negative values if the iterator is an input iterator. Also note that std::next requires the iterator to meet the conditions of an ForwardIterator, while std::advance only needs an Input Iterator (if you don't use negative distances).
This question already has answers here:
Is list::size() really O(n)?
(8 answers)
Closed 4 years ago.
This code ran for 0.012 seconds:
std::list<int> list;
list.resize(100);
int size;
for(int i = 0 ; i < 10000; i++)
size = list.size();
This one for 9.378 seconds:
std::list<int> list;
list.resize(100000);
int size;
for(int i = 0 ; i < 10000; i++)
size = list.size();
In my opinion it would be possible to implement std::list in such way, that size would be stored in a private variable but according to this it is computed again each time I call size. Can anyone explain why?
There is a conflict between constant time size() and constant time list.splice. The committee chose to favour splice.
When you splice nodes between two lists, you would have to count the nodes moved to update the sizes of the two lists. That takes away a lot of the advantage of splicing nodes by just changing a few internal pointers.
As noted in the comments, C++11 has changed this by giving up O(1) for some rare(?) uses of splice:
void splice(const_iterator position, list& x, const_iterator first, const_iterator last);
void splice(const_iterator position, list&& x, const_iterator first, const_iterator last);
Complexity: Constant time if &x == this; otherwise, linear time.
In ISO/IEC 14882:2011, §C.2.12, Clause 23: "containers library":
Change: Complexity of size() member functions now constant
Rationale: Lack of specification of complexity of size() resulted in divergent implementations with inconsistent performance characteristics.
Effect on original feature: Some container implementations that conform to C++ 2003 may not conform to the specified size() requirements in this International Standard. Adjusting containers such as std::list to the stricter requirements may require incompatible changes.
For the comments:
In 23.3.5.5 - "list operations", again in ISO/IEC 14882:2011:
list provides three splice operations that destructively move elements from one list to another. The behavior of splice operations is undefined if get_allocator() != x.get_allocator().
void splice(const_iterator position, list& x);
void splice(const_iterator position, list&& x);
Requires: &x != this.
Effects: Inserts the contents of x before position and x becomes empty. Pointers and references to the moved elements of x now refer to those same elements but as members of *this. Iterators referringto the moved elements will continue to refer to their elements, but they now behave as iterators into *this, not into x.
Complexity: Constant time.
void splice(const_iterator position, list& x, const_iterator i);
void splice(const_iterator position, list&& x, const_iterator i);
Effects: Inserts an element pointed to by i from list x before position and removes the element from x. The result is unchanged if position == i or position == ++i. Pointers and references to *i continue to refer to this same element but as a member of *this. Iterators to *i (including i itself) continue to refer to the same element, but now behave as iterators into *this, not into x.
Requires: i is a valid dereferenceable iterator of x.
Complexity: Constant time.
void splice(const_iterator position, list& x, const_iterator first, const_iterator last);
void splice(const_iterator position, list&& x, const_iterator first, const_iterator last);
Effects: Inserts elements in the range [first,last) before position and removes the elements from x.
Requires: [first, last) is a valid range in x. The result is undefined if position is an iterator in the range [first,last). Pointers and references to the moved elements of x now refer to those same elements but as members of *this. Iterators referring to the moved elements will continue to refer to their elements, but they now behave as iterators into *this, not into x.
Complexity: Constant time if &x == this; otherwise, linear time.