3

Is there any way how to use std::rotate for the list

std::list<int> v = { 0,7, 1,2 };

since these left/right rotations

std::rotate(v.begin(), v.begin() + 1, v.end());
std::rotate(v.rbegin(), v.rbegin() + 1, v.rend());

work for the vector?

std::vector<int> v = { 0, 7, 1, 2 };

One possible way is to copy the list to the vector

std::vector<int> u{ std::begin(v), std::end(v) };

and vice versa but I found it too "lengthy"... A direct rotation of the list leads to the following errors:

Error   C2672   'std::rotate': no matching overloaded function found    
Error   C2676   binary '+':  std::_List_iterator<std::_List_val<std::_List_simple_types<_Ty>>>' does not define this operator or a conversion to a type acceptable to the predefined operator

Thanks for your help.

JFMR
  • 23,265
  • 4
  • 52
  • 76
justik
  • 4,145
  • 6
  • 32
  • 53
  • 5
    Your issue is not rotate, but the iterator type; `std::list` has [BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator). They don't implement `operator+`. You have to iterate to the point you want to have as new first... http://coliru.stacked-crooked.com/a/633363afbc94a64a – user1810087 Aug 28 '18 at 07:53
  • 1
    BTW: You could simply implement an `operator+` overload for your needs; [quick and dirty](http://coliru.stacked-crooked.com/a/c1646818e9382e92). – user1810087 Aug 28 '18 at 08:09

2 Answers2

8

You can't add to std::list iterator since it's not random access. But you can increment it. And that's what std::next does for you:

template< class Item >
void rot_slow( std::list<Item>& seq )
{
    std::rotate( seq.begin(), next( seq.begin() ), seq.end() );
}

However, this logic, using std::rotate, uses O(n) swap operations.

That's needlessly inefficient. If you want to rotate through all items in the list that's O(n²) complexity. It quickly gets very slow.

Instead just splice the first item in at the end of the list:

template< class Item >
void rot_fast( std::list<Item>& seq )
{
    seq.splice( seq.end(), seq, seq.begin() );
}

This uses 0 item swaps, O(1) complexity.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Somehow I was expecting/hoping that rotate has an overload for list iterators ... – Raffi Jun 29 '19 at 23:08
  • This is undefined behaviour. See the standard. **The behavior is undefined if other refers to the same object as *this.** https://en.cppreference.com/w/cpp/container/list/splice – bradgonesurfing Aug 09 '22 at 08:32
  • 1
    @bradgonesurfing: Thanks, but the UB for splicing from self only applies to the overloads with 2 arguments, i.e. attempting to move all of the list into a position in the list (which is generally impossible). It's easy to misunderstand. In fact, at first I corrected the code and wrote: ❝Thanks, you're right, splicing from itself is UB. That was surprising restriction. I've corrected the code to just use a temporary list.❞ – Cheers and hth. - Alf Aug 10 '22 at 09:40
6

The only syntactical issue with the invocation

 std::rotate(v.begin(), v.begin() + 1, v.end());

is that std::list iterators don't model random access iterators but bidirectional iterators. Therefore, you can't add or subtract integral values to/from them. Instead, call std::rotate like this

std::rotate(v.begin(), std::next(v.begin()), v.end());
std::rotate(v.rbegin(), std::next(v.rbegin()), v.rend());

Here, std::next increments your iterator, no matter what concept it satifies. That's why it's sometimes better to use it in the first place (in your case, when using a std::vector), as it adds one level of indirection as opposed to someIterator + 1, where you hard-wire the random access requirement.

lubgr
  • 37,368
  • 3
  • 66
  • 117