diff --git a/source/simple/support/misc.hpp b/source/simple/support/misc.hpp index 85d6cfeafe807a419b461e9c96a04e68cfc7eb6c..1ac05d92b98f14672727295dfb070220c2e4bcd6 100644 --- a/source/simple/support/misc.hpp +++ b/source/simple/support/misc.hpp @@ -9,6 +9,7 @@ #include <cinttypes> #include "range.hpp" +#include "algorithm/utils.hpp" namespace simple::support { @@ -129,11 +130,64 @@ namespace simple::support } template <typename It> + [[deprecated("use simple::support::string_view from simple/support/algorithm/range_wrappers.hpp")]] std::string_view make_string_view(It begin, It end) { return {begin, static_cast<std::string_view::size_type>(end - begin)}; } + // NOTE: maybe put in algorithm range wrappers instead? + template <typename Container, + typename = std::enable_if_t<is_range_v<Container>>> + class index_range + { + public: + + using container_type = Container; + using size_type = typename container_type::size_type; + explicit index_range + ( + Container & container, + range<size_type> index = range<size_type>::limit() + ) : + container(&container), + index(index) + {} + + explicit index_range + ( + Container & container, + range<typename Container::const_iterator> iterator_range + ) : + container(&container), + index(iterator_range - container.begin()) + {} + + auto begin() const + { return iter_at(index.lower()); } + auto end() const + { return iter_at(index.upper()); } + + private: + Container* container; + range<size_type> index; + + auto iter_at(size_type index) const + { + using std::begin; + using std::end; + using std::clamp; + + auto begin_ = begin(*container); + auto end_ = end(*container); + + // TODO: use container.size() if available + return begin_ + clamp(index, size_type{}, + static_cast<size_type>(end_ - begin_)); // good cast, since container can not have negative size + } + + }; + } // namespace simple::support #endif /* end of include guard */ diff --git a/unit_tests/misc.cpp b/unit_tests/misc.cpp index 429cac9be10472fb2a2cd70420cbb0d01ef15aef..e2954606c4fcda6bc0c19cbb2e83ec5a70a9ae1d 100644 --- a/unit_tests/misc.cpp +++ b/unit_tests/misc.cpp @@ -101,11 +101,47 @@ void SimplifiedToNumber() to_<unsigned char>(std::to_string(std::numeric_limits<unsigned char>::max()) + '0')); } +void RangeReference() +{ + using std::begin; + using std::end; + + const std::string prefix = "Permaprefix"; + + std::string stuff{prefix}; + + range temporef{begin(stuff), end(stuff)}; + + assert( std::equal(begin(temporef), end(temporef), + begin(prefix), end(prefix)) ); + + index_range permaref(stuff, {begin(stuff), end(stuff)}); + + assert( std::equal(begin(permaref), end(permaref), + begin(prefix), end(prefix)) ); + + stuff += " now this a really really long long paragraph of text, that should by all means cause reallocation and stuff, so the iterators in temporef range will be invalidated, while permaref that uses indices will remain valid and usable... though it doesn't really matter if it actually relocates since there is no way to check any of this, you'll just have to believe me and trust me and never ever let me go..."; + + // can't do this anymore + // assert( std::equal(begin(temporef), end(temporef), + // begin(prefix), end(prefix)) ); + // asan might not catch this, because of small string optimization, and since you are allowed to reinterpret anything as char* it might not even be UB dependent on the implementation, but then the assertion will fail at least + // otherwise the assertion might not fail, if the old/freed memory happens to remain untouched by either the system or the allocator + // so asan and assert together can kind of catch this I guess + // TODO: __gnu_debug::string (but not std::string under _GLIBCXX_DEBUG, it's an obscure exception) does catch this reliably, so that's a thing to consider for unit testing purposes in general + + // all ok here + assert( std::equal(begin(permaref), end(permaref), + begin(prefix), end(prefix)) ); + +} + int main() { StringToNumber(); StringToNumericRange(); NumericRangeToString(); SimplifiedToNumber(); + RangeReference(); return 0; }