Skip to content

Commit

Permalink
Define starflate::{copy,copy_n}
Browse files Browse the repository at this point in the history
`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
oliverlee committed Oct 21, 2023
1 parent 87d0405 commit bc35ef4
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ package(default_visibility = ["//src:__subpackages__"])

cc_library(
name = "decompress",
hdrs = ["decompress.hpp"],
hdrs = [
"copy.hpp",
"decompress.hpp",
],
)
135 changes: 135 additions & 0 deletions src/copy.hpp
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);

Check warning on line 47 in src/copy.hpp

View check run for this annotation

Codecov / codecov/patch

src/copy.hpp#L47

Added line #L47 was not covered by tests
}

// 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

Check warning on line 119 in src/copy.hpp

View check run for this annotation

Codecov / codecov/patch

src/copy.hpp#L119

Added line #L119 was not covered by tests
-> std::ranges::subrange<O>
{
const auto n = std::ranges::ssize(source);
assert(

Check warning on line 123 in src/copy.hpp

View check run for this annotation

Codecov / codecov/patch

src/copy.hpp#L122-L123

Added lines #L122 - L123 were not covered by tests
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());

Check warning on line 128 in src/copy.hpp

View check run for this annotation

Codecov / codecov/patch

src/copy.hpp#L128

Added line #L128 was not covered by tests

assert(result.in == std::ranges::cend(source));
return dest.next(n);

Check warning on line 131 in src/copy.hpp

View check run for this annotation

Codecov / codecov/patch

src/copy.hpp#L130-L131

Added lines #L130 - L131 were not covered by tests
}
} copy{};

} // namespace starflate
10 changes: 10 additions & 0 deletions src/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,13 @@ cc_test(
"//src:decompress",
],
)

cc_test(
name = "copy_test",
timeout = "short",
srcs = ["copy_test.cpp"],
deps = [
"//:boost_ut",
"//src:decompress",
],
)
135 changes: 135 additions & 0 deletions src/test/copy_test.cpp
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});
}));
};
}

0 comments on commit bc35ef4

Please sign in to comment.