Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define starflate::{copy,copy_n} #109

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
],
)
137 changes: 137 additions & 0 deletions src/copy.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#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
}

// FIXME this is potential UB
// https://stackoverflow.com/questions/56036264/what-is-the-rationale-of-making-subtraction-of-two-pointers-not-related-to-the-s
// https://stackoverflow.com/questions/47616508/what-is-the-rationale-for-limitations-on-pointer-arithmetic-or-comparison
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 121 in src/copy.hpp

View check run for this annotation

Codecov / codecov/patch

src/copy.hpp#L121

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

Check warning on line 125 in src/copy.hpp

View check run for this annotation

Codecov / codecov/patch

src/copy.hpp#L124-L125

Added lines #L124 - L125 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 130 in src/copy.hpp

View check run for this annotation

Codecov / codecov/patch

src/copy.hpp#L130

Added line #L130 was not covered by tests

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

Check warning on line 133 in src/copy.hpp

View check run for this annotation

Codecov / codecov/patch

src/copy.hpp#L132-L133

Added lines #L132 - L133 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});
}));
};
}
Loading