Skip to content

Commit 4d05b45

Browse files
committed
Make timsort work without postfix ++ and -- on iterators
1 parent 61556cf commit 4d05b45

File tree

3 files changed

+208
-44
lines changed

3 files changed

+208
-44
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ Additionally `gfx::timsort` can take a [projection function](https://ezoeryou.gi
1818
after the comparison function. The support is a bit rougher than in the linked article or the C++20 stadard library:
1919
only instances of types callable with parentheses can be used, there is no support for pointer to members.
2020

21+
This implementation of timsort notably avoids using the postfix `++` or `--` operators: only their prefix equivalents
22+
are used, which means that timsort will work even if the postfix operators are not present or return an incompatible
23+
type such as `void`.
24+
2125
## EXAMPLE
2226

2327
Example of using timsort with a comparison function and a projection function to sort a vector of strings by length:

include/gfx/timsort.hpp

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,15 @@ template <typename RandomAccessIterator, typename Compare> class TimSort {
201201
return 1;
202202
}
203203

204-
if (compare(*(runHi++), *lo)) { // decreasing
205-
while (runHi < hi && compare(*runHi, *(runHi - 1))) {
204+
if (compare(*runHi, *lo)) { // decreasing
205+
do {
206206
++runHi;
207-
}
207+
} while (runHi < hi && compare(*runHi, *(runHi - 1)));
208208
std::reverse(lo, runHi);
209209
} else { // non-decreasing
210-
while (runHi < hi && !compare(*runHi, *(runHi - 1))) {
210+
do {
211211
++runHi;
212-
}
212+
} while (runHi < hi && !compare(*runHi, *(runHi - 1)));
213213
}
214214

215215
return runHi - lo;
@@ -445,7 +445,9 @@ template <typename RandomAccessIterator, typename Compare> class TimSort {
445445
iter_t cursor2 = base2;
446446
iter_t dest = base1;
447447

448-
*(dest++) = GFX_TIMSORT_MOVE(*(cursor2++));
448+
*dest = GFX_TIMSORT_MOVE(*cursor2);
449+
++cursor2;
450+
++dest;
449451
--len2;
450452

451453
int minGallop(minGallop_);
@@ -460,14 +462,18 @@ template <typename RandomAccessIterator, typename Compare> class TimSort {
460462
GFX_TIMSORT_ASSERT(len2 > 0);
461463

462464
if (compare(*cursor2, *cursor1)) {
463-
*(dest++) = GFX_TIMSORT_MOVE(*(cursor2++));
465+
*dest = GFX_TIMSORT_MOVE(*cursor2);
466+
++cursor2;
467+
++dest;
464468
++count2;
465469
count1 = 0;
466470
if (--len2 == 0) {
467471
goto epilogue;
468472
}
469473
} else {
470-
*(dest++) = GFX_TIMSORT_MOVE(*(cursor1++));
474+
*dest = GFX_TIMSORT_MOVE(*cursor1);
475+
++cursor1;
476+
++dest;
471477
++count1;
472478
count2 = 0;
473479
if (--len1 == 1) {
@@ -491,7 +497,9 @@ template <typename RandomAccessIterator, typename Compare> class TimSort {
491497
goto epilogue;
492498
}
493499
}
494-
*(dest++) = GFX_TIMSORT_MOVE(*(cursor2++));
500+
*dest = GFX_TIMSORT_MOVE(*cursor2);
501+
++cursor2;
502+
++dest;
495503
if (--len2 == 0) {
496504
goto epilogue;
497505
}
@@ -506,7 +514,9 @@ template <typename RandomAccessIterator, typename Compare> class TimSort {
506514
goto epilogue;
507515
}
508516
}
509-
*(dest++) = GFX_TIMSORT_MOVE(*(cursor1++));
517+
*dest = GFX_TIMSORT_MOVE(*cursor1);
518+
++cursor1;
519+
++dest;
510520
if (--len1 == 1) {
511521
goto epilogue;
512522
}
@@ -554,7 +564,8 @@ template <typename RandomAccessIterator, typename Compare> class TimSort {
554564
tmp_iter_t cursor2 = tmp_.begin() + (len2 - 1);
555565
iter_t dest = base2 + (len2 - 1);
556566

557-
*(dest--) = GFX_TIMSORT_MOVE(*(--cursor1));
567+
*dest = GFX_TIMSORT_MOVE(*(--cursor1));
568+
--dest;
558569
--len1;
559570

560571
int minGallop(minGallop_);
@@ -575,15 +586,18 @@ template <typename RandomAccessIterator, typename Compare> class TimSort {
575586
GFX_TIMSORT_ASSERT(len2 > 1);
576587

577588
if (compare(*cursor2, *cursor1)) {
578-
*(dest--) = GFX_TIMSORT_MOVE(*cursor1);
589+
*dest = GFX_TIMSORT_MOVE(*cursor1);
590+
--dest;
579591
++count1;
580592
count2 = 0;
581593
if (--len1 == 0) {
582594
goto epilogue;
583595
}
584596
--cursor1;
585597
} else {
586-
*(dest--) = GFX_TIMSORT_MOVE(*(cursor2--));
598+
*dest = GFX_TIMSORT_MOVE(*cursor2);
599+
--cursor2;
600+
--dest;
587601
++count2;
588602
count1 = 0;
589603
if (--len2 == 1) {
@@ -609,7 +623,9 @@ template <typename RandomAccessIterator, typename Compare> class TimSort {
609623
goto epilogue;
610624
}
611625
}
612-
*(dest--) = GFX_TIMSORT_MOVE(*(cursor2--));
626+
*dest = GFX_TIMSORT_MOVE(*cursor2);
627+
--cursor2;
628+
--dest;
613629
if (--len2 == 1) {
614630
goto epilogue;
615631
}
@@ -624,12 +640,13 @@ template <typename RandomAccessIterator, typename Compare> class TimSort {
624640
goto epilogue;
625641
}
626642
}
627-
*(dest--) = GFX_TIMSORT_MOVE(*(--cursor1));
643+
*dest = GFX_TIMSORT_MOVE(*(--cursor1));
644+
--dest;
628645
if (--len1 == 0) {
629646
goto epilogue;
630647
}
631648

632-
minGallop--;
649+
--minGallop;
633650
} while ((count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP));
634651

635652
if (minGallop < 0) {

tests/cxx_98_tests.cpp

Lines changed: 171 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,167 @@
1212
#include <catch.hpp>
1313
#include <gfx/timsort.hpp>
1414

15+
namespace {
16+
// Helper types for the tests
17+
18+
////////////////////////////////////////////////////////////
19+
// Timsort should work with types that are not
20+
// default-constructible
21+
22+
struct NonDefaultConstructible {
23+
int i;
24+
25+
NonDefaultConstructible(int i_) : i(i_) {
26+
}
27+
28+
friend bool operator<(NonDefaultConstructible const &x, NonDefaultConstructible const &y) {
29+
return x.i < y.i;
30+
}
31+
};
32+
33+
////////////////////////////////////////////////////////////
34+
// Tools to test the stability of the sort
35+
36+
enum id { foo, bar, baz };
37+
38+
typedef std::pair<int, id> pair_t;
39+
40+
inline bool less_in_first(pair_t x, pair_t y) {
41+
return x.first < y.first;
42+
}
43+
44+
////////////////////////////////////////////////////////////
45+
// Timsort should work with iterators that don't have a
46+
// post-increment or post-decrement operation
47+
48+
template<typename Iterator>
49+
class NoPostIterator
50+
{
51+
public:
52+
53+
////////////////////////////////////////////////////////////
54+
// Public types
55+
56+
typedef typename std::iterator_traits<Iterator>::iterator_category iterator_category;
57+
typedef Iterator iterator_type;
58+
typedef typename std::iterator_traits<Iterator>::value_type value_type;
59+
typedef typename std::iterator_traits<Iterator>::difference_type difference_type;
60+
typedef typename std::iterator_traits<Iterator>::pointer pointer;
61+
typedef typename std::iterator_traits<Iterator>::reference reference;
62+
63+
////////////////////////////////////////////////////////////
64+
// Constructors
65+
66+
NoPostIterator() = default;
67+
68+
explicit NoPostIterator(Iterator it) : _it(it) {
69+
}
70+
71+
////////////////////////////////////////////////////////////
72+
// Members access
73+
74+
iterator_type base() const {
75+
return _it;
76+
}
77+
78+
////////////////////////////////////////////////////////////
79+
// Element access
80+
81+
reference operator*() const {
82+
return *base();
83+
}
84+
85+
pointer operator->() const {
86+
return &(operator*());
87+
}
88+
89+
////////////////////////////////////////////////////////////
90+
// Increment/decrement operators
91+
92+
NoPostIterator& operator++() {
93+
++_it;
94+
return *this;
95+
}
96+
97+
NoPostIterator& operator--() {
98+
--_it;
99+
return *this;
100+
}
101+
102+
NoPostIterator& operator+=(difference_type increment) {
103+
_it += increment;
104+
return *this;
105+
}
106+
107+
NoPostIterator& operator-=(difference_type increment) {
108+
_it -= increment;
109+
return *this;
110+
}
111+
112+
////////////////////////////////////////////////////////////
113+
// Comparison operators
114+
115+
friend bool operator==(NoPostIterator const& lhs, NoPostIterator const& rhs) {
116+
return lhs.base() == rhs.base();
117+
}
118+
119+
friend bool operator!=(NoPostIterator const& lhs, NoPostIterator const& rhs) {
120+
return lhs.base() != rhs.base();
121+
}
122+
123+
////////////////////////////////////////////////////////////
124+
// Relational operators
125+
126+
friend bool operator<(NoPostIterator const& lhs, NoPostIterator const& rhs) {
127+
return lhs.base() < rhs.base();
128+
}
129+
130+
friend bool operator<=(NoPostIterator const& lhs, NoPostIterator const& rhs) {
131+
return lhs.base() <= rhs.base();
132+
}
133+
134+
friend bool operator>(NoPostIterator const& lhs, NoPostIterator const& rhs) {
135+
return lhs.base() > rhs.base();
136+
}
137+
138+
friend bool operator>=(NoPostIterator const& lhs, NoPostIterator const& rhs) {
139+
return lhs.base() >= rhs.base();
140+
}
141+
142+
////////////////////////////////////////////////////////////
143+
// Arithmetic operators
144+
145+
friend NoPostIterator operator+(NoPostIterator it, difference_type size) {
146+
return it += size;
147+
}
148+
149+
friend NoPostIterator operator+(difference_type size, NoPostIterator it) {
150+
return it += size;
151+
}
152+
153+
friend NoPostIterator operator-(NoPostIterator it, difference_type size) {
154+
return it -= size;
155+
}
156+
157+
friend difference_type operator-(NoPostIterator const& lhs, NoPostIterator const& rhs) {
158+
return lhs.base() - rhs.base();
159+
}
160+
161+
private:
162+
163+
Iterator _it;
164+
};
165+
166+
////////////////////////////////////////////////////////////
167+
// Construction function
168+
169+
template<typename Iterator>
170+
NoPostIterator<Iterator> make_no_post_iterator(Iterator it) {
171+
return NoPostIterator<Iterator>(it);
172+
}
173+
174+
}
175+
15176
TEST_CASE( "simple0" ) {
16177
std::vector<int> a;
17178

@@ -358,17 +519,6 @@ TEST_CASE( "string_array" ) {
358519
CHECK(a[4] == "9");
359520
}
360521

361-
struct NonDefaultConstructible {
362-
int i;
363-
364-
NonDefaultConstructible(int i_) : i(i_) {
365-
}
366-
367-
friend bool operator<(NonDefaultConstructible const &x, NonDefaultConstructible const &y) {
368-
return x.i < y.i;
369-
}
370-
};
371-
372522
TEST_CASE( "non_default_constructible" ) {
373523
NonDefaultConstructible a[] = {7, 1, 5, 3, 9};
374524

@@ -398,14 +548,6 @@ TEST_CASE( "default_compare_function" ) {
398548
}
399549
}
400550

401-
enum id { foo, bar, baz };
402-
403-
typedef std::pair<int, id> pair_t;
404-
405-
inline bool less_in_first(pair_t x, pair_t y) {
406-
return x.first < y.first;
407-
}
408-
409551
TEST_CASE( "stability" ) {
410552
std::vector<pair_t> a;
411553

@@ -446,13 +588,6 @@ TEST_CASE( "stability" ) {
446588
CHECK(a[11].second == baz);
447589
}
448590

449-
inline bool less_in_pair(const std::pair<int, int> &x, const std::pair<int, int> &y) {
450-
if (x.first == y.first) {
451-
return x.second < y.second;
452-
}
453-
return x.first < y.first;
454-
}
455-
456591
TEST_CASE( "issue2_duplication" ) {
457592
std::vector<std::pair<int, int> > a;
458593

@@ -465,8 +600,8 @@ TEST_CASE( "issue2_duplication" ) {
465600

466601
std::vector<std::pair<int, int> > expected(a);
467602

468-
std::sort(expected.begin(), expected.end(), &less_in_pair);
469-
gfx::timsort(a.begin(), a.end(), &less_in_pair);
603+
std::sort(expected.begin(), expected.end());
604+
gfx::timsort(a.begin(), a.end());
470605

471606
#if 0
472607
for (std::size_t i = 0; i < a.size(); ++i) {
@@ -531,3 +666,11 @@ TEST_CASE( "projection" ) {
531666
CHECK(vec[i] == i - 40);
532667
}
533668
}
669+
670+
TEST_CASE( "iterator without post-increment or post-decrement" ) {
671+
std::vector<int> a;
672+
673+
gfx::timsort(make_no_post_iterator(a.begin()), make_no_post_iterator(a.end()));
674+
675+
CHECK(a.size() == std::size_t(0));
676+
}

0 commit comments

Comments
 (0)