From a50de751b4a6c94e5ab78b0f99a1d810b8b12e61 Mon Sep 17 00:00:00 2001
From: Sam Yates <halfflat@gmail.com>
Date: Tue, 4 Oct 2016 18:24:17 +0200
Subject: [PATCH] Can't std::sort on forward iterators.

But GNU libstc++ tries to anyway, hilarious segfaults
ensue.

* Don't try and sort a sentinel-terminated range.
* Instead provide a `strict_view` that walks a range
  (if required) and presents a range with a proper
  end iterator.
* Tests, const range (but non-const iterator) implementations.
---
 src/util/range.hpp        | 18 ++++++++++++++++++
 src/util/rangeutil.hpp    | 26 ++++++++++++++++++++++++--
 tests/unit/CMakeLists.txt | 12 +-----------
 tests/unit/test_range.cpp | 17 +++++++++++++++--
 4 files changed, 58 insertions(+), 15 deletions(-)

diff --git a/src/util/range.hpp b/src/util/range.hpp
index 2ed703df..6c161fda 100644
--- a/src/util/range.hpp
+++ b/src/util/range.hpp
@@ -151,6 +151,9 @@ range<U, V> make_range(const U& left, const V& right) {
     return range<U, V>(left, right);
 }
 
+// Present a possibly sentinel-terminated range as an STL-compatible sequence
+// using the sentinel_iterator adaptor.
+
 template <typename Seq>
 auto canonical_view(Seq& s) ->
     range<sentinel_iterator_t<decltype(std::begin(s)), decltype(std::end(s))>>
@@ -165,6 +168,21 @@ auto canonical_view(const Seq& s) ->
     return {make_sentinel_iterator(std::begin(s), std::end(s)), make_sentinel_end(std::begin(s), std::end(s))};
 }
 
+// Strictly evaluate end point in sentinel-terminated range and present as a range over
+// iterators. Note: O(N) behaviour with forward iterator ranges or sentinel-terminated ranges.
+
+template <typename Seq>
+auto strict_view(Seq& s) -> range<decltype(std::begin(s))>
+{
+    return make_range(std::begin(s), std::next(util::upto(std::begin(s), std::end(s))));
+}
+
+template <typename Seq>
+auto strict_view(const Seq& s) -> range<decltype(std::begin(s))>
+{
+    return make_range(std::begin(s), std::next(util::upto(std::begin(s), std::end(s))));
+}
+
 } // namespace util
 } // namespace mc
 } // namespace nest
diff --git a/src/util/rangeutil.hpp b/src/util/rangeutil.hpp
index 95200b9c..d87edc93 100644
--- a/src/util/rangeutil.hpp
+++ b/src/util/rangeutil.hpp
@@ -75,9 +75,18 @@ AssignableContainer& assign_by(AssignableContainer& c, const Seq& seq, const Pro
 }
 
 // Sort in-place
+// Note that a const range reference may wrap non-const iterators.
 
 template <typename Seq>
-void sort(Seq& seq) {
+enable_if_t<!std::is_const<typename util::sequence_traits<Seq>::reference>::value>
+sort(Seq& seq) {
+    auto canon = canonical_view(seq);
+    std::sort(std::begin(canon), std::end(canon));
+}
+
+template <typename Seq>
+enable_if_t<!std::is_const<typename util::sequence_traits<Seq>::reference>::value>
+sort(const Seq& seq) {
     auto canon = canonical_view(seq);
     std::sort(std::begin(canon), std::end(canon));
 }
@@ -85,7 +94,20 @@ void sort(Seq& seq) {
 // Sort in-place by projection `proj`
 
 template <typename Seq, typename Proj>
-void sort_by(Seq& seq, const Proj& proj) {
+enable_if_t<!std::is_const<typename util::sequence_traits<Seq>::reference>::value>
+sort_by(Seq& seq, const Proj& proj) {
+    using value_type = typename sequence_traits<Seq>::value_type;
+    auto canon = canonical_view(seq);
+
+    std::sort(std::begin(canon), std::end(canon),
+        [&proj](const value_type& a, const value_type& b) {
+            return proj(a) < proj(b);
+        });
+}
+
+template <typename Seq, typename Proj>
+enable_if_t<!std::is_const<typename util::sequence_traits<Seq>::reference>::value>
+sort_by(const Seq& seq, const Proj& proj) {
     using value_type = typename sequence_traits<Seq>::value_type;
     auto canon = canonical_view(seq);
 
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index 1c286863..b67683ec 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -1,13 +1,3 @@
-set(HEADERS
-    ${PROJECT_SOURCE_DIR}/src/cell.hpp
-    ${PROJECT_SOURCE_DIR}/src/cell_tree.hpp
-    ${PROJECT_SOURCE_DIR}/src/math.hpp
-    ${PROJECT_SOURCE_DIR}/src/point.hpp
-    ${PROJECT_SOURCE_DIR}/src/segment.hpp
-    ${PROJECT_SOURCE_DIR}/src/swcio.hpp
-    ${PROJECT_SOURCE_DIR}/src/tree.hpp
-)
-
 set(TEST_SOURCES
     # unit tests
     test_algorithms.cpp
@@ -47,7 +37,7 @@ set(TEST_SOURCES
 )
 
 add_definitions("-DDATADIR=\"${CMAKE_SOURCE_DIR}/data\"")
-add_executable(test.exe ${TEST_SOURCES} ${HEADERS})
+add_executable(test.exe ${TEST_SOURCES})
 
 set(TARGETS test.exe)
 
diff --git a/tests/unit/test_range.cpp b/tests/unit/test_range.cpp
index 3cf2893e..1f1e3cb6 100644
--- a/tests/unit/test_range.cpp
+++ b/tests/unit/test_range.cpp
@@ -184,6 +184,16 @@ TEST(range, sentinel) {
     EXPECT_EQ(0u, empty_cstr_range.size());
 }
 
+TEST(range, strictify) {
+    const char *cstr = "hello world";
+    auto cstr_range = util::make_range(cstr, null_terminated);
+
+    auto ptr_range = util::strict_view(cstr_range);
+    EXPECT_TRUE((std::is_same<decltype(ptr_range), util::range<const char *>>::value));
+    EXPECT_EQ(cstr, ptr_range.left);
+    EXPECT_EQ(cstr+11, ptr_range.right);
+}
+
 template <typename V>
 class counter_range: public ::testing::Test {};
 
@@ -290,12 +300,15 @@ TEST(range, sort) {
 
     auto cstr_range = util::make_range(std::begin(cstr), null_terminated);
 
+    // Alas, no forward_iterator sort yet, so make a strict (non-sentinel)
+    // range to sort on below
+
     // simple sort
-    util::sort(cstr_range);
+    util::sort(util::strict_view(cstr_range));
     EXPECT_EQ(std::string("dhowy"), cstr);
 
     // reverse sort by transform c to -c
-    util::sort_by(cstr_range, [](char c) { return -c; });
+    util::sort_by(util::strict_view(cstr_range), [](char c) { return -c; });
     EXPECT_EQ(std::string("ywohd"), cstr);
 }
 
-- 
GitLab