From f65a70c3e0dd9175e13b5af0d9e2abc8d73bcebf Mon Sep 17 00:00:00 2001
From: namark <namark@disroot.org>
Date: Mon, 20 Sep 2021 19:50:57 +0400
Subject: [PATCH] Range with compile time lower bound,
also make_range removed in favor of CTAD, since we are knee deep in
c++17 already.
---
source/simple/support/algorithm/utils.hpp | 4 +-
source/simple/support/algorithm/variance.hpp | 7 +-
source/simple/support/range.hpp | 156 +++++++++++++------
unit_tests/random.cpp | 13 +-
unit_tests/range.cpp | 42 ++++-
5 files changed, 164 insertions(+), 58 deletions(-)
diff --git a/source/simple/support/algorithm/utils.hpp b/source/simple/support/algorithm/utils.hpp
index 2da9355..f231a13 100644
--- a/source/simple/support/algorithm/utils.hpp
+++ b/source/simple/support/algorithm/utils.hpp
@@ -20,7 +20,7 @@ namespace simple::support
{
using std::begin;
using std::end;
- return make_range(begin(container), end(container));
+ return range{begin(container), end(container)};
}
template <typename Container>
@@ -32,7 +32,7 @@ namespace simple::support
{
using std::rbegin;
using std::rend;
- return make_range(rbegin(container), rend(container));
+ return range{rbegin(container), rend(container)};
}
template <typename Container>
diff --git a/source/simple/support/algorithm/variance.hpp b/source/simple/support/algorithm/variance.hpp
index 60cdf3c..b1e7902 100644
--- a/source/simple/support/algorithm/variance.hpp
+++ b/source/simple/support/algorithm/variance.hpp
@@ -1,6 +1,7 @@
#ifndef SIMPLE_SUPPORT_ALGORITHM_VARIANCE_HPP
#define SIMPLE_SUPPORT_ALGORITHM_VARIANCE_HPP
-#include "iterator"
+#include "../range.hpp"
+#include <iterator>
namespace simple::support
{
@@ -30,7 +31,7 @@ namespace simple::support
{
using std::begin;
using std::end;
- return make_range( begin(range), variance(begin(range), end(range), bop) );
+ return support::range{ begin(range), variance(begin(range), end(range), bop) };
}
template <typename Range>
@@ -38,7 +39,7 @@ namespace simple::support
{
using std::begin;
using std::end;
- return make_range( begin(range), variance(begin(range), end(range)) );
+ return support::range{ begin(range), variance(begin(range), end(range)) };
}
} // namespace simple::support
diff --git a/source/simple/support/range.hpp b/source/simple/support/range.hpp
index 2c5c9c4..b92a208 100644
--- a/source/simple/support/range.hpp
+++ b/source/simple/support/range.hpp
@@ -3,8 +3,7 @@
#include <algorithm>
#include <type_traits>
-#include <istream>
-#include <ostream>
+#include <cassert>
#include "array.hpp"
#include "array_operators.hpp"
@@ -25,26 +24,25 @@ namespace simple::support
: public std::true_type
{};
- template <typename Type>
+ template <typename Type, typename Bounds = array<Type,2>>
struct range
{
- array<Type, 2> bounds;
+ Bounds bounds;
using iterator = std::conditional_t<is_iterable<Type>{},
Type, void>;
- constexpr static range limit()
+ constexpr static auto limit()
{
static_assert(std::numeric_limits<Type>::is_specialized);
- return { std::numeric_limits<Type>::lowest(), std::numeric_limits<Type>::max() };
+ return support::range{ std::numeric_limits<Type>::lowest(), std::numeric_limits<Type>::max() };
}
-
- constexpr Type& lower() noexcept { return bounds[0]; }
- constexpr Type& upper() noexcept { return bounds[1]; }
- constexpr const Type& lower() const noexcept { return bounds[0]; }
- constexpr const Type& upper() const noexcept { return bounds[1]; }
+ constexpr auto& lower() noexcept { return bounds[0]; }
+ constexpr auto& upper() noexcept { return bounds[1]; }
+ constexpr const auto& lower() const noexcept { return bounds[0]; }
+ constexpr const auto& upper() const noexcept { return bounds[1]; }
constexpr bool operator ==(const range& other) const
{ return bounds == other.bounds; }
@@ -67,16 +65,16 @@ namespace simple::support
[[nodiscard]] constexpr auto rend() const
{ return std::make_reverse_iterator(lower()); }
- template<typename OtherType>
+ template<typename OtherType, typename OtherBounds = Bounds>
[[nodiscard]] constexpr
- range sub_range(range<OtherType> other) const
+ auto sub_range(range<OtherType, OtherBounds> other) const
{
// TODO: use unsigned for other type when it makes sense, to avoid overflow
clamp_in_place(other, {
OtherType{},
static_cast<OtherType>(upper() - lower())
});
- return
+ return support::range
{
lower() + other.lower(),
lower() + other.upper()
@@ -110,9 +108,15 @@ namespace simple::support
}
[[nodiscard]]
- constexpr range fixed() const
+ constexpr auto fixed() const
{
- return range{*this}.fix();
+ using std::min;
+ using std::max;
+ return support::range
+ {
+ min(lower(), upper()),
+ max(lower(), upper())
+ };
}
[[nodiscard]]
@@ -137,6 +141,7 @@ namespace simple::support
constexpr bool intersects_upper(const ValueType& value) const
{ return lower() < value && value <= upper(); }
+ // TODO: make these more generic, compatible Type, any Bounds
constexpr bool contains(const range& other) const
{ return lower() < other.lower() && other.upper() < upper(); }
@@ -159,9 +164,15 @@ namespace simple::support
return *this;
}
- constexpr range intersection(range other) const
+ constexpr auto intersection(const range& other) const
{
- return other.intersect(*this);
+ using std::min;
+ using std::max;
+ return support::range
+ {
+ max(lower(), other.lower()),
+ min(upper(), other.upper())
+ };
}
template <typename Other, std::enable_if_t<
@@ -194,50 +205,76 @@ namespace simple::support
template <typename T> range(T lower, T upper) -> range<T>;
// TODO: generalize some of these to anything that has lower()/upper() bound iterface
- template <typename Type, typename ValueType = Type, std::common_type_t<ValueType, Type>* = nullptr>
- constexpr bool contains(const range<Type>& range, const ValueType& value)
+ // and implement the members based on these instead of these based on members
+ template <typename Type, typename Bounds = array<Type,2>, typename ValueType = Type, std::common_type_t<ValueType, Type>* = nullptr>
+ constexpr bool contains(const range<Type,Bounds>& range, const ValueType& value)
{ return range.contains(value); }
- template <typename Type, typename ValueType, std::common_type_t<ValueType, Type>* = nullptr>
- constexpr bool intersects(const range<Type>& range, const ValueType& value)
+ template <typename Type, typename ValueType, typename Bounds = array<Type,2>, std::common_type_t<ValueType, Type>* = nullptr>
+ constexpr bool intersects(const range<Type,Bounds>& range, const ValueType& value)
{ return range.intersects(value); }
- template <typename Type, typename ValueType, std::common_type_t<ValueType, Type>* = nullptr>
- constexpr bool intersects_lower(const range<Type>& range, const ValueType& value)
+ template <typename Type, typename ValueType, typename Bounds = array<Type,2>, std::common_type_t<ValueType, Type>* = nullptr>
+ constexpr bool intersects_lower(const range<Type,Bounds>& range, const ValueType& value)
{ return range.intersects_lower(value); }
- template <typename Type, typename ValueType, std::common_type_t<ValueType, Type>* = nullptr>
- constexpr bool intersects_upper(const range<Type>& range, const ValueType& value)
+ // TODO: here to enable implicit conversion when Type is specified, but this whole business is really dodgy
+ template <typename Type, typename ValueType = Type, std::common_type_t<ValueType, Type>* = nullptr>
+ constexpr bool intersects_lower(const non_deduced<range<Type>>& range, const non_deduced<ValueType>& value)
+ { return intersects_lower(range, value); }
+
+ template <typename Type, typename ValueType, typename Bounds = array<Type,2>, std::common_type_t<ValueType, Type>* = nullptr>
+ constexpr bool intersects_upper(const range<Type,Bounds>& range, const ValueType& value)
{ return range.intersects_upper(value); }
template <typename Type>
constexpr bool contains(const range<Type>& one, const range<Type>& other)
{ return one.contains(other); }
+ template <typename Type, typename Bounds = array<Type,2>>
+ constexpr bool contains(const range<Type,Bounds>& one, const range<Type,Bounds>& other)
+ { return one.contains(other); }
+
template <typename Type>
constexpr bool covers(const range<Type>& one, const range<Type>& other)
{ return one.covers(other); }
+ template <typename Type, typename Bounds = array<Type,2>>
+ constexpr bool covers(const range<Type,Bounds>& one, const range<Type,Bounds>& other)
+ { return one.covers(other); }
+
template <typename Type>
constexpr bool overlaps(const range<Type>& one, const range<Type>& other)
{ return one.overlaps(other); }
+ template <typename Type, typename Bounds = array<Type,2>>
+ constexpr bool overlaps(const range<Type,Bounds>& one, const range<Type,Bounds>& other)
+ { return one.overlaps(other); }
+
template <typename Type>
constexpr bool intersects(const range<Type>& one, const range<Type>& other)
{ return one.intersects(other); }
+ template <typename Type, typename Bounds = array<Type,2>>
+ constexpr bool intersects(const range<Type,Bounds>& one, const range<Type,Bounds>& other)
+ { return one.intersects(other); }
+
template <typename Type>
- constexpr range<Type> intersection(const range<Type>& one, range<Type> other)
+ constexpr auto intersection(const range<Type>& one, range<Type> other)
{ return one.intersection(other); }
- template <typename Type>
+ template <typename Type, typename Bounds = array<Type,2>>
+ constexpr auto intersection(const range<Type,Bounds>& one, range<Type,Bounds> other)
+ { return one.intersection(other); }
+
+ template <typename Type, typename Bounds = array<Type,2>>
[[nodiscard]]
- constexpr auto reverse(const range<Type>& one)
+ constexpr auto reverse(const range<Type, Bounds>& one)
{ return one.reverse(); }
- template <typename Type>
+ template <typename Type, typename Bounds = array<Type,2>>
constexpr
- range<Type>& clamp_in_place(range<Type>& v, const range<Type>& hilo)
+ range<Type>& clamp_in_place(range<Type,Bounds>& v, const range<Type,Bounds>& hilo)
{
using std::clamp;
for(size_t i = 0; i < v.bounds.size(); ++i)
@@ -245,45 +282,76 @@ namespace simple::support
return v;
}
- template <typename Type, typename RangeValueType = Type>
+ template <typename Type, typename RangeValueType = Type, typename RangeBounds = array<RangeValueType,2>>
constexpr
- Type& clamp_in_place(Type& v, const range<RangeValueType>& hilo)
+ Type& clamp_in_place(Type& v, const range<RangeValueType, RangeBounds>& hilo)
{
using std::clamp;
v = clamp(v, hilo.lower(), hilo.upper());
return v;
}
+ // TODO: here to enable implicit conversion when Type is specified, but this whole business is really dodgy
template <typename Type, typename RangeValueType = Type>
constexpr
- Type clamp(Type v, const range<RangeValueType>& hilo)
+ Type& clamp_in_place(non_deduced<Type>& v, const non_deduced<range<RangeValueType>>& hilo)
+ {
+ return clamp_in_place(v,hilo);
+ }
+
+ template <typename Type, typename RangeValueType = Type, typename RangeBounds = array<RangeValueType, 2>>
+ constexpr
+ Type clamp(Type v, const range<RangeValueType,RangeBounds>& hilo)
{
clamp_in_place(v, hilo);
return v;
}
- template <typename Type>
+ template <typename Type, typename Bounds = array<Type,2>>
constexpr
- range<Type> clamp(range<Type> v, const range<Type>& hilo)
+ range<Type> clamp(range<Type,Bounds> v, const range<Type,Bounds>& hilo)
{
clamp_in_place(v, hilo);
return v;
}
- template<typename T>
- constexpr range<T> make_range(T&& lower, T&& upper)
+ class range_operators_compatibility_tag {};
+
+ template <typename Type, typename Lower>
+ struct range_upper_bound
+ {
+ Type upper;
+
+ constexpr const Type& operator[](size_t i) const
+ {
+ assert(intersects_lower(range{size_t{},size()}, i));
+ if(i == 0)
+ return Lower::value;
+ else
+ return upper;
+ };
+
+ constexpr size_t size() const { return 2; }
+
+ constexpr bool operator==(range_upper_bound other)
+ { return upper == other.upper; }
+ constexpr bool operator!=(range_upper_bound other)
+ { return !(*this == other); }
+ };
+
+ template<typename Lower, typename T = typename Lower::value_type>
+ constexpr range<T, range_upper_bound<T, Lower>> make_range_upper(T&& upper)
{
- return range<T>{std::forward<T>(lower), std::forward<T>(upper)};
+ return {std::forward<T>(upper)};
}
- class range_operators_compatibility_tag {};
} // namespace simple::support
namespace simple
{
- template<typename T>
- struct support::define_array_operators<support::range<T>>
+ template<typename T, typename B>
+ struct support::define_array_operators<support::range<T,B>>
{
constexpr static auto enabled_operators = array_operator::none;
constexpr static auto enabled_right_element_operators =
@@ -298,9 +366,9 @@ namespace simple
constexpr static auto enabled_left_element_operators = array_operator::none;
constexpr static size_t size = 2;
- constexpr static array<T,size>& get_array(support::range<T>& r) noexcept
+ constexpr static auto& get_array(support::range<T,B>& r) noexcept
{ return r.bounds; }
- constexpr static const array<T,size>& get_array(const support::range<T>& r) noexcept
+ constexpr static const auto& get_array(const support::range<T,B>& r) noexcept
{ return r.bounds; }
template <typename R, array_operator, typename, bool>
diff --git a/unit_tests/random.cpp b/unit_tests/random.cpp
index 6f0df64..9069bdd 100644
--- a/unit_tests/random.cpp
+++ b/unit_tests/random.cpp
@@ -1,16 +1,17 @@
// TODO: need a test file per header and include the header first, to detect missing includes
-#include <iostream>
-#include <sstream>
-#include <cassert>
-#include <random>
-#include <unordered_map>
#include "simple/support/random/engine/tiny.hpp"
#include "simple/support/random/distribution/naive.hpp"
#include "simple/support/random/distribution/diagonal.hpp"
#include "simple/support/misc.hpp"
#include "simple/support/algorithm.hpp"
+#include <iostream>
+#include <sstream>
+#include <cassert>
+#include <random>
+#include <unordered_map>
+
using namespace simple::support;
using namespace random;
using namespace distribution;
@@ -58,7 +59,7 @@ entropy_result get_entropy(PRNG prng)
unsigned char* bytes = reinterpret_cast<unsigned char*>(data.data());
const std::size_t byte_size = data.size() * sizeof(typename decltype(data)::value_type);
- auto byte_range = make_range(+bytes, bytes + byte_size);
+ auto byte_range = range{+bytes, bytes + byte_size};
result.base = bit_entropy(data);
result.byte_base = bit_entropy(byte_range);
diff --git a/unit_tests/range.cpp b/unit_tests/range.cpp
index 31f6ccd..fa1da4f 100644
--- a/unit_tests/range.cpp
+++ b/unit_tests/range.cpp
@@ -1,7 +1,8 @@
+#include "simple/support/range.hpp"
+
#include <cassert>
#include <random>
#include <iostream>
-#include "simple/support/range.hpp"
using namespace simple::support;
@@ -38,8 +39,8 @@ void SetLikePredicates()
range r{-13,31};
assert(contains(r, 2));
- assert(!contains(r, 45));
assert(!contains(r, r.lower()));
+ assert(!contains(r, 45));
assert(!contains(r, r.upper()));
assert(intersects(r, 12));
@@ -304,6 +305,40 @@ constexpr bool Constexprness()
return true;
}
+void UpperRange()
+{
+ auto valid_upper_range = make_range_upper<std::integral_constant<int,-3>>(12);
+ assert(valid_upper_range.valid());
+ assert(( valid_upper_range + 3 == range{0,15} ));
+
+ struct { int x; } one_int;
+ static_assert(sizeof(valid_upper_range) == sizeof(one_int));
+
+ auto invalid_upper_range = make_range_upper<std::integral_constant<int,13>>(12);
+ assert(!invalid_upper_range.valid());
+ assert(( invalid_upper_range.fixed() == range{12,13} ));
+
+ // TODO: rest of const stuff should work
+
+}
+
+struct rango
+{
+ operator range<int> () { return range{1,2}; }
+};
+
+void ImplicitConversionSupport() // TODO: not sure about this...
+{
+ int a = 13;
+ clamp_in_place<int>(a, rango{});
+ assert(2 == a);
+ assert(not intersects_lower<int>(rango{},a));
+
+ int b = -1;
+ clamp_in_place<int>(b, rango{});
+ assert(intersects_lower<int>(rango{},b));
+}
+
int main()
{
Validity();
@@ -312,7 +347,8 @@ int main()
SubRange();
Limit();
Arithmetic();
- Iterator();
static_assert(Constexprness());
+ UpperRange();
+ ImplicitConversionSupport();
return 0;
}
--
GitLab