diff --git a/src/BUILD.bazel b/src/BUILD.bazel index b447bf4..3bcff66 100644 --- a/src/BUILD.bazel +++ b/src/BUILD.bazel @@ -4,5 +4,8 @@ package(default_visibility = ["//src:__subpackages__"]) cc_library( name = "decompress", - hdrs = ["decompress.hpp"], + hdrs = [ + "copy_n.hpp", + "decompress.hpp", + ], ) diff --git a/src/copy_n.hpp b/src/copy_n.hpp new file mode 100644 index 0000000..496de01 --- /dev/null +++ b/src/copy_n.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace starflate { + +/// Return type of `copy_n` +/// @tparam I input_iterator of the source range +/// @tparam O output_iterator of the destination range +/// +template +using copy_n_result = std::ranges::in_out_result; + +/// 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 + static constexpr auto + impl(std::true_type, I first, D n, O result) -> copy_n_result + { + 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); + } + + const auto dist = result - first; + + while (n != D{}) { + const auto m = std::min(dist, n); + std::memcpy(result, first, static_cast(m) * sizeof(*first)); + first += m; + result += m; + n -= m; + } + + return {first, result}; + } + + template + static constexpr auto + impl(std::false_type, I first, D n, O result) -> copy_n_result + { + while (n-- != D{}) { + *result++ = *first++; + } + + return {first, result}; + } + +public: + template + requires std::indirectly_copyable + constexpr auto + operator()(I first, std::iter_difference_t n, O result) const + -> copy_n_result + { + using try_bulk_copy = std::bool_constant< + std::contiguous_iterator and // + std::contiguous_iterator and // + std::is_same_v, std::iter_value_t> and // + std::is_trivially_copyable_v>>; + + return impl(try_bulk_copy{}, first, n, result); + } +} copy_n{}; + +} // namespace starflate diff --git a/src/test/BUILD.bazel b/src/test/BUILD.bazel index 6b44e93..681a2fb 100644 --- a/src/test/BUILD.bazel +++ b/src/test/BUILD.bazel @@ -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", + ], +) diff --git a/src/test/copy_n_test.cpp b/src/test/copy_n_test.cpp new file mode 100644 index 0000000..7e7c3de --- /dev/null +++ b/src/test/copy_n_test.cpp @@ -0,0 +1,73 @@ +#include "src/copy_n.hpp" + +#include + +#include +#include +#include +#include + +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{}; + starflate::copy_n(data.begin() + 3, 5, data.begin()); + })); + }; +}