Skip to content

Commit 72043c7

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

File tree

3 files changed

+209
-44
lines changed

3 files changed

+209
-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: 172 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,168 @@
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() : _it() {
67+
}
68+
69+
explicit NoPostIterator(Iterator it) : _it(it) {
70+
}
71+
72+
////////////////////////////////////////////////////////////
73+
// Members access
74+
75+
iterator_type base() const {
76+
return _it;
77+
}
78+
79+
////////////////////////////////////////////////////////////
80+
// Element access
81+
82+
reference operator*() const {
83+
return *base();
84+
}
85+
86+
pointer operator->() const {
87+
return &(operator*());
88+
}
89+
90+
////////////////////////////////////////////////////////////
91+
// Increment/decrement operators
92+
93+
NoPostIterator& operator++() {
94+
++_it;
95+
return *this;
96+
}
97+
98+
NoPostIterator& operator--() {
99+
--_it;
100+
return *this;
101+
}
102+
103+
NoPostIterator& operator+=(difference_type increment) {
104+
_it += increment;
105+
return *this;
106+
}
107+
108+
NoPostIterator& operator-=(difference_type increment) {
109+
_it -= increment;
110+
return *this;
111+
}
112+
113+
////////////////////////////////////////////////////////////
114+
// Comparison operators
115+
116+
friend bool operator==(NoPostIterator const& lhs, NoPostIterator const& rhs) {
117+
return lhs.base() == rhs.base();
118+
}
119+
120+
friend bool operator!=(NoPostIterator const& lhs, NoPostIterator const& rhs) {
121+
return lhs.base() != rhs.base();
122+
}
123+
124+
////////////////////////////////////////////////////////////
125+
// Relational operators
126+
127+
friend bool operator<(NoPostIterator const& lhs, NoPostIterator const& rhs) {
128+
return lhs.base() < rhs.base();
129+
}
130+
131+
friend bool operator<=(NoPostIterator const& lhs, NoPostIterator const& rhs) {
132+
return lhs.base() <= rhs.base();
133+
}
134+
135+
friend bool operator>(NoPostIterator const& lhs, NoPostIterator const& rhs) {
136+
return lhs.base() > rhs.base();
137+
}
138+
139+
friend bool operator>=(NoPostIterator const& lhs, NoPostIterator const& rhs) {
140+
return lhs.base() >= rhs.base();
141+
}
142+
143+
////////////////////////////////////////////////////////////
144+
// Arithmetic operators
145+
146+
friend NoPostIterator operator+(NoPostIterator it, difference_type size) {
147+
return it += size;
148+
}
149+
150+
friend NoPostIterator operator+(difference_type size, NoPostIterator it) {
151+
return it += size;
152+
}
153+
154+
friend NoPostIterator operator-(NoPostIterator it, difference_type size) {
155+
return it -= size;
156+
}
157+
158+
friend difference_type operator-(NoPostIterator const& lhs, NoPostIterator const& rhs) {
159+
return lhs.base() - rhs.base();
160+
}
161+
162+
private:
163+
164+
Iterator _it;
165+
};
166+
167+
////////////////////////////////////////////////////////////
168+
// Construction function
169+
170+
template<typename Iterator>
171+
NoPostIterator<Iterator> make_no_post_iterator(Iterator it) {
172+
return NoPostIterator<Iterator>(it);
173+
}
174+
175+
}
176+
15177
TEST_CASE( "simple0" ) {
16178
std::vector<int> a;
17179

@@ -358,17 +520,6 @@ TEST_CASE( "string_array" ) {
358520
CHECK(a[4] == "9");
359521
}
360522

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-
372523
TEST_CASE( "non_default_constructible" ) {
373524
NonDefaultConstructible a[] = {7, 1, 5, 3, 9};
374525

@@ -398,14 +549,6 @@ TEST_CASE( "default_compare_function" ) {
398549
}
399550
}
400551

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-
409552
TEST_CASE( "stability" ) {
410553
std::vector<pair_t> a;
411554

@@ -446,13 +589,6 @@ TEST_CASE( "stability" ) {
446589
CHECK(a[11].second == baz);
447590
}
448591

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-
456592
TEST_CASE( "issue2_duplication" ) {
457593
std::vector<std::pair<int, int> > a;
458594

@@ -465,8 +601,8 @@ TEST_CASE( "issue2_duplication" ) {
465601

466602
std::vector<std::pair<int, int> > expected(a);
467603

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

471607
#if 0
472608
for (std::size_t i = 0; i < a.size(); ++i) {
@@ -531,3 +667,11 @@ TEST_CASE( "projection" ) {
531667
CHECK(vec[i] == i - 40);
532668
}
533669
}
670+
671+
TEST_CASE( "iterator without post-increment or post-decrement" ) {
672+
std::vector<int> a;
673+
674+
gfx::timsort(make_no_post_iterator(a.begin()), make_no_post_iterator(a.end()));
675+
676+
CHECK(a.size() == std::size_t(0));
677+
}

0 commit comments

Comments
 (0)