Skip to content

Commit

Permalink
Define starflate::copy_n
Browse files Browse the repository at this point in the history
Define a variant of the `copy_n` algorithm for copying bytes in the
output stream during decode of a compressed block. `copy_n` specifically
handles overlap of the source and destination ranges.

Change-Id: I756a8416a82f4a2007c7f2461de04353aac667d6
  • Loading branch information
oliverlee committed Oct 21, 2023
1 parent 87d0405 commit 5730ed5
Show file tree
Hide file tree
Showing 4 changed files with 184 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_n.hpp",
"decompress.hpp",
],
)
97 changes: 97 additions & 0 deletions src/copy_n.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#pragma once

#include <algorithm>
#include <cassert>
#include <concepts>
#include <cstring>
#include <iterator>
#include <ranges>
#include <type_traits>

namespace starflate {

/// Return type of `copy_n`
/// @tparam I input_iterator of the source range
/// @tparam O output_iterator of the destination range
///
template <class I, class O>
using copy_n_result = std::ranges::in_out_result<I, O>;

/// 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)`.
///
/// @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) -> copy_n_result<I, O>
{
assert((result + n <= first or first < result) and
"destination range overlaps left side of source range");

if (std::is_constant_evaluated()) {
return impl(std::false_type{}, first, n, result);

Check warning on line 53 in src/copy_n.hpp

View check run for this annotation

Codecov / codecov/patch

src/copy_n.hpp#L53

Added line #L53 was not covered by tests
}

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) -> copy_n_result<I, O>
{
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
-> copy_n_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{};

} // 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_n_test",
timeout = "short",
srcs = ["copy_n_test.cpp"],
deps = [
"//:boost_ut",
"//src:decompress",
],
)
73 changes: 73 additions & 0 deletions src/test/copy_n_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "src/copy_n.hpp"

#include <boost/ut.hpp>

#include <algorithm>
#include <array>
#include <iterator>
#include <list>

auto main() -> int
{
using ::boost::ut::aborts;
using ::boost::ut::expect;
using ::boost::ut::range_eq;
using ::boost::ut::test;

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};
starflate::copy_n(data.begin(), 5, data.begin() + 3);
return data;
}();

expect(range_eq(expected, data));
};

test("copy with overlap, contiguous range, dest overlaps left side of "
"source") = [] {
expect(aborts([] {
auto data = std::array<int, 8>{};
starflate::copy_n(data.begin() + 3, 5, data.begin());
}));
};
}

0 comments on commit 5730ed5

Please sign in to comment.