C++'s std::rotate

Andrei Alexandrescu via Digitalmars-d digitalmars-d at puremagic.com
Sun Aug 10 20:29:59 PDT 2014


Hello,


In the discussion of iterators vs. ranges in D, ranges "won" in the 
sense that there hasn't been strong evidence of a need for iterators to 
complement ranges; people have merrily used std.algorithm and friends 
and all was good.

However, it has been long acknowledged that iterators are sometimes more 
expressive/economical than ranges, in the same way pointers are more so 
than slices: iterators are a lower-level building block so they can be 
used to build ranges and also other things. There's an inclusion 
relationship between what can be done with iterators and what can be 
done with ranges.

Amid these observations, C++'s std::rotate (http://goo.gl/z8FjV) and 
derivative algorithms have been a prime category of examples. C++'s 
std::rotate takes a "three-legged range", i.e. three iterators begin, 
middle, and end, and rotates the range [middle, end) to precede [begin, 
middle). It returns (as of C++11) the new middle. For example, given the 
range [0, 1, 2, 3, 4, 5] with begin -> 0, mid -> 2, and end -> just past 
5, rotate rearranges elements as [2, 3, 4, 5, 0, 1, 2] and returns a 
pointer to the new position of 0.

This is a very challenging algorithm for ranges because it's difficult 
to express both the input and the output appropriately. My first insight 
into the matter has been that the ranges [begin, middle) and [middle, 
end) don't even need to be adjacent, so I defined bringToFront 
(http://goo.gl/5HUCiV) as a slightly different take on std::rotate. It 
rotates any two ranges, adjacent or not, and returns the number of 
elements in the right-hand range.

That's in a way more general than std::rotate but at the same time less 
satisfactory because it returns only a number, giving no access to the 
new positions of the two elements.

Following an exchange with Eric Niebler I've scored one more 
mini-breakthrough today: a variant of std::rotate that rivals C++'s one, 
without needing iterators. The only abstraction needed is 
r1.sameFront(r2), which returns true iff the forward ranges r1 and r2 
have fronts pointing to the same element. The function can be 
implemented generically for ranges that offer front as a reference:

bool sameFront(R1, R2)(R1 r1, R2 r2) {
   return &r1.front == &r2.front;
}

Ranges that offer rvalues from front (proxying etc) must implement 
sameFront as a primitive.

Implementation is at http://dpaste.dzfl.pl/a0effbaee0a9. For historical 
reasons I've reused an undocumented function sameHead. It has the 
meaning of sameFront above. Signature is:

Tuple!(typeof(whole.takeExactly(0)), R)
rotateTail(R)(R whole, R right);

The algorithm assumes that "right" is a subrange of "whole" sitting at 
its tail, i.e. calling while.popFront a number of times will make 
whole.sameFront(right) true. It moves right to the front of whole and 
returns (here's the beauty of it) the new positions of the two subranges.

For example, say whole = [0, 1, 2, 3, 4, 5] and right = whole[4 .. $]. 
Then rotateTail(whole, right) makes whole = [4, 5, 0, 1, 2, 3] and 
returns tuple(whole[0 .. 2], whole[2 .. $]).

An essential role in this all is takeExactly (just like take, but knows 
the length in O(1)). It's been an essential component in a few 
range-based algorithms and it seems an important abstraction that closes 
the circle on rotateTail as well, almost too fittingly. Eric mentioned 
that he independently saw a need for it and defined what he calls 
SizedIteratorRange.


Andrei


More information about the Digitalmars-d mailing list