diff --git a/source/simple/support/algorithm/utils.hpp b/source/simple/support/algorithm/utils.hpp index 2da935599bc402c0b613df9e3f6c847ac5c87bb9..f231a1366918c0eeadc48c34344459e050930ff8 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 60cdf3c54a6c519a41b0a5a69a2c4fa9a8c21aa2..b1e7902be87faac77a3a4175a76c2ff0ab1d14a0 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 2c5c9c4e6d969f2d5e4a2541207ea5cb41296d79..b92a208ffbf2df3783f3b8ec92d30de056c0908c 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 6f0df6427bb9d4cf8e702866aa29406d573adae5..9069bddff5490ebd52a69bdaaf29d93e5cc83947 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 31f6ccdbc08a94a75bb6cbe0950ef5442ee4b339..fa1da4fac1b4f46f200f012a8eb911be3225b5e3 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; }