-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
`starflate::{copy,copy_n}` are variants of the standard library `copy` and `copy_n` algorithms but modified for use in DEFLATE decoding. Both of these algorithms specifically handle overlap of the source and destination ranges. `starflate::copy_n` takes an input iterator, distance, and output iterator, `starflate::copy` takes a source range and a destination range, returning the unwritten subrange of the destination range. Change-Id: I756a8416a82f4a2007c7f2461de04353aac667d6
- Loading branch information
Showing
4 changed files
with
284 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
#pragma once | ||
|
||
#include <algorithm> | ||
#include <cassert> | ||
#include <concepts> | ||
#include <cstdint> | ||
#include <cstring> | ||
#include <iterator> | ||
#include <ranges> | ||
#include <type_traits> | ||
|
||
namespace starflate { | ||
|
||
/// Copies a number of elements to a new location | ||
/// @tparam I input_iterator of the source range | ||
/// @tparam O output_iterator of the destination range | ||
/// @param first beginning of the range to copy from | ||
/// @param n number of element to copy | ||
/// @param result beginning of the destination range | ||
/// | ||
/// Copies exactly `n` values from the range beginning at first to the range | ||
/// beginning at `result` by performing the equivalent of `*(result + i) = | ||
/// *(first + i)` for each integer in [`0`, `n`). | ||
/// | ||
/// @return `std::ranges::in_out_result` that contains an `std::input_iterator` | ||
/// iterator equal to `std::ranges::next(first, n)` and a | ||
/// `std::weakly_incrementable` iterator equal to `ranges::next(result, n)`. | ||
/// | ||
/// @pre `n >= 0` | ||
/// @pre destination range does not overlap left side of source range | ||
/// | ||
/// @note Unlike `std::copy_n` and `std::ranges::copy_n`, `starflate::copy_n` | ||
/// specifically handles a destination range overlapping the right side of a | ||
/// source range. | ||
/// | ||
/// @note This is implemented as a global function object. | ||
/// | ||
/// @see https://en.cppreference.com/w/cpp/algorithm/copy_n | ||
/// | ||
inline constexpr class | ||
{ | ||
template <class I, class D, class O> | ||
static constexpr auto impl(std::true_type, I first, D n, O result) | ||
-> std::ranges::in_out_result<I, O> | ||
{ | ||
if (std::is_constant_evaluated()) { | ||
return impl(std::false_type{}, first, n, result); | ||
} | ||
|
||
// TODO is this potentially UB? | ||
const auto dist = result - first; | ||
|
||
while (n != D{}) { | ||
const auto m = std::min(dist, n); | ||
std::memcpy(result, first, static_cast<std::size_t>(m) * sizeof(*first)); | ||
first += m; | ||
result += m; | ||
n -= m; | ||
} | ||
|
||
return {first, result}; | ||
} | ||
|
||
template <class I, class D, class O> | ||
static constexpr auto impl(std::false_type, I first, D n, O result) | ||
-> std::ranges::in_out_result<I, O> | ||
{ | ||
assert(n >= D{} and "`n` must be non-negative"); | ||
|
||
while (n-- != D{}) { | ||
*result++ = *first++; | ||
} | ||
|
||
return {first, result}; | ||
} | ||
|
||
public: | ||
template <std::input_iterator I, std::weakly_incrementable O> | ||
requires std::indirectly_copyable<I, O> | ||
constexpr auto | ||
operator()(I first, std::iter_difference_t<I> n, O result) const | ||
-> std::ranges::in_out_result<I, O> | ||
{ | ||
using try_bulk_copy = std::bool_constant< | ||
std::contiguous_iterator<I> and // | ||
std::contiguous_iterator<O> and // | ||
std::is_same_v<std::iter_value_t<I>, std::iter_value_t<O>> and // | ||
std::is_trivially_copyable_v<std::iter_value_t<I>>>; | ||
|
||
return impl(try_bulk_copy{}, first, n, result); | ||
} | ||
} copy_n{}; | ||
|
||
/// Copies a source range into the beginning of a destination range | ||
/// @tparam R source range | ||
/// @tparam O iterator type of destination range | ||
/// @param source range to copy from | ||
/// @param dest range to copy to | ||
/// | ||
/// Copies bytes from the source range to the destination range. | ||
/// | ||
/// @return `std::ranges::subrange` containing the unwritten subrange of | ||
/// `dest` | ||
/// | ||
/// @pre `std::ranges::size(source) <= dest.size()` and destination range does | ||
/// not overlap left side of source range. | ||
/// | ||
/// @note `starflate::copy_n` specifically handles a destination range | ||
/// overlapping the right side of a source range. | ||
/// | ||
/// @note This is implemented as a global function object. | ||
/// | ||
inline constexpr class | ||
{ | ||
public: | ||
template <std::ranges::sized_range R, std::random_access_iterator O> | ||
requires std::indirectly_copyable<std::ranges::iterator_t<R>, O> | ||
constexpr auto | ||
operator()(const R& source, std::ranges::subrange<O> dest) const | ||
-> std::ranges::subrange<O> | ||
{ | ||
const auto n = std::ranges::ssize(source); | ||
assert( | ||
n <= std::ranges::ssize(dest) and | ||
"destination range is smaller " | ||
"than source range"); | ||
|
||
const auto result = copy_n(std::ranges::cbegin(source), n, dest.begin()); | ||
|
||
assert(result.in == std::ranges::cend(source)); | ||
return dest.next(n); | ||
} | ||
} copy{}; | ||
|
||
} // namespace starflate |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
#include "src/copy.hpp" | ||
|
||
#include <boost/ut.hpp> | ||
|
||
#include <algorithm> | ||
#include <array> | ||
#include <cassert> | ||
#include <iterator> | ||
#include <list> | ||
|
||
auto main() -> int | ||
{ | ||
using ::boost::ut::aborts; | ||
using ::boost::ut::expect; | ||
using ::boost::ut::range_eq; | ||
using ::boost::ut::test; | ||
using std::ranges::subrange; | ||
|
||
test("copy with overlap, non-contiguous range") = [] { | ||
constexpr auto expected = std::array{1, 2, 3, 1, 2, 3, 1, 2}; | ||
|
||
auto data = std::list{1, 2, 3, 0, 0, 0, 0, 0}; | ||
|
||
const auto result = | ||
starflate::copy_n(data.begin(), 5, std::next(data.begin(), 3)); | ||
|
||
expect(std::next(data.begin(), 5) == result.in); | ||
expect(data.end() == result.out); | ||
expect(range_eq(expected, data)); | ||
}; | ||
|
||
test("copy, adjacent ranges, contiguous range") = [] { | ||
constexpr auto expected = std::array{1, 2, 3, 4, 1, 2, 3, 4}; | ||
|
||
auto data = std::array{1, 2, 3, 4, 0, 0, 0, 0}; | ||
|
||
auto result = starflate::copy_n(data.begin(), 4, data.begin() + 4); | ||
|
||
expect(data.begin() + 4 == result.in); | ||
expect(data.end() == result.out); | ||
expect(range_eq(expected, data)); | ||
}; | ||
|
||
test("copy with overlap, contiguous range") = [] { | ||
constexpr auto expected = std::array{1, 2, 3, 1, 2, 3, 1, 2}; | ||
|
||
auto data = std::array{1, 2, 3, 0, 0, 0, 0, 0}; | ||
|
||
auto result = starflate::copy_n(data.begin(), 5, data.begin() + 3); | ||
|
||
expect(data.begin() + 5 == result.in); | ||
expect(data.end() == result.out); | ||
expect(range_eq(expected, data)); | ||
}; | ||
|
||
test("copy with overlap, contiguous, constexpr") = [] { | ||
constexpr auto expected = std::array{1, 2, 3, 1, 2, 3, 1, 2}; | ||
|
||
constexpr auto data = [] { | ||
auto data = std::array{1, 2, 3, 0, 0, 0, 0, 0}; | ||
|
||
auto result = starflate::copy_n(data.begin(), 5, data.begin() + 3); | ||
assert(result.out == data.cend()); | ||
|
||
return data; | ||
}(); | ||
|
||
expect(range_eq(expected, data)); | ||
}; | ||
|
||
test("different ranges without overlap") = [] { | ||
static constexpr auto expected = std::array{1, 2, 3, 4}; | ||
|
||
constexpr auto actual = [] { | ||
auto buffer = std::array<int, 6>{}; | ||
|
||
const auto dest = subrange{buffer}; | ||
|
||
auto remaining = starflate::copy(expected, dest); | ||
assert(remaining.begin() == dest.begin() + expected.size()); | ||
assert(remaining.size() == dest.size() - expected.size()); | ||
|
||
return buffer; | ||
}(); | ||
|
||
using std::views::take; | ||
expect(range_eq(expected, actual | take(4))); | ||
}; | ||
|
||
test("same range without overlap") = [] { | ||
static constexpr auto expected = std::array{1, 2, 3, 1, 2, 3}; | ||
|
||
constexpr auto actual = [] { | ||
auto buffer = std::array<int, 6>{1, 2, 3}; | ||
|
||
const auto src = subrange{buffer.cbegin(), buffer.cbegin() + 3}; | ||
const auto dest = subrange{buffer}.next(3); | ||
|
||
auto remaining = starflate::copy(src, dest); | ||
assert(remaining.empty()); | ||
|
||
return buffer; | ||
}(); | ||
|
||
expect(range_eq(expected, actual)); | ||
}; | ||
|
||
test("same range with overlap") = [] { | ||
static constexpr auto expected = std::array{1, 2, 1, 2, 1, 0}; | ||
|
||
constexpr auto actual = [] { | ||
auto buffer = std::array<int, 6>{1, 2}; | ||
|
||
const auto src = subrange{buffer.cbegin(), buffer.cbegin() + 3}; | ||
const auto dest = subrange{buffer}.next(2); | ||
|
||
auto remaining = starflate::copy(src, dest); | ||
assert(remaining.size() == dest.size() - src.size()); | ||
assert(remaining.begin() == buffer.cbegin() + 5); | ||
|
||
return buffer; | ||
}(); | ||
|
||
expect(range_eq(expected, actual)); | ||
}; | ||
|
||
test("destination range too small") = [] { | ||
static constexpr auto expected = std::array<int, 4>{}; | ||
|
||
expect(aborts([] { | ||
auto buffer = std::array<int, 3>{}; | ||
starflate::copy(expected, subrange{buffer}); | ||
})); | ||
}; | ||
} |