Revision control
Copy as Markdown
Other Tools
/*
* (C) 2023-2024 René Meusel - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
#ifndef BOTAN_CONCAT_UTIL_H_
#define BOTAN_CONCAT_UTIL_H_
#include <botan/assert.h>
#include <botan/concepts.h>
#include <botan/range_concepts.h>
#include <botan/strong_type.h>
#include <array>
#include <iterator>
#include <ranges>
#include <span>
#include <tuple>
namespace Botan {
namespace detail {
/**
* Helper function that performs range size-checks as required given the
* selected output and input range types. If all lengths are known at compile
* time, this check will be performed at compile time as well. It will then
* instantiate an output range and concatenate the input ranges' contents.
*/
template <ranges::spanable_range OutR, ranges::spanable_range... Rs>
constexpr OutR concatenate(Rs&&... ranges)
requires(concepts::reservable_container<OutR> || ranges::statically_spanable_range<OutR>)
{
OutR result{};
// Prepare and validate the output range and construct a lambda that does the
// actual filling of the result buffer.
// (if no input ranges are given, GCC claims that fill_fn is unused)
[[maybe_unused]] auto fill_fn = [&] {
if constexpr(concepts::reservable_container<OutR>) {
// dynamically allocate the correct result byte length
const size_t total_size = (ranges.size() + ... + 0);
result.reserve(total_size);
// fill the result buffer using a back-inserter
return [&result](auto&& range) {
std::copy(
std::ranges::begin(range), std::ranges::end(range), std::back_inserter(unwrap_strong_type(result)));
};
} else {
if constexpr((ranges::statically_spanable_range<Rs> && ... && true)) {
// all input ranges have a static extent, so check the total size at compile time
// (work around an issue in MSVC that warns `total_size` is unused)
[[maybe_unused]] constexpr size_t total_size = (decltype(std::span{ranges})::extent + ... + 0);
static_assert(result.size() == total_size, "size of result buffer does not match the sum of input buffers");
} else {
// at least one input range has a dynamic extent, so check the total size at runtime
const size_t total_size = (ranges.size() + ... + 0);
BOTAN_ARG_CHECK(result.size() == total_size,
"result buffer has static extent that does not match the sum of input buffers");
}
// fill the result buffer and hold the current output-iterator position
return [itr = std::ranges::begin(result)](auto&& range) mutable {
std::copy(std::ranges::begin(range), std::ranges::end(range), itr);
std::advance(itr, std::ranges::size(range));
};
}
}();
// perform the actual concatenation
(fill_fn(std::forward<Rs>(ranges)), ...);
return result;
}
} // namespace detail
/**
* Concatenate an arbitrary number of buffers. Performs range-checks as needed.
*
* The output type can be auto-detected based on the input ranges, or explicitly
* specified by the caller. If all input ranges have a static extent, the total
* size is calculated at compile time and a statically sized std::array<> is used.
* Otherwise this tries to use the type of the first input range as output type.
*
* Alternatively, the output container type can be specified explicitly.
*/
template <typename OutR = detail::AutoDetect, ranges::spanable_range... Rs>
constexpr auto concat(Rs&&... ranges)
requires(all_same_v<std::ranges::range_value_t<Rs>...>)
{
if constexpr(std::same_as<detail::AutoDetect, OutR>) {
// Try to auto-detect a reasonable output type given the input ranges
static_assert(sizeof...(Rs) > 0, "Cannot auto-detect the output type if not a single input range is provided.");
using candidate_result_t = std::remove_cvref_t<std::tuple_element_t<0, std::tuple<Rs...>>>;
using result_range_value_t = std::remove_cvref_t<std::ranges::range_value_t<candidate_result_t>>;
if constexpr((ranges::statically_spanable_range<Rs> && ...)) {
// If all input ranges have a static extent, we can calculate the total size at compile time
// and therefore can use a statically sized output container. This is constexpr.
constexpr size_t total_size = (decltype(std::span{ranges})::extent + ... + 0);
using out_array_t = std::array<result_range_value_t, total_size>;
return detail::concatenate<out_array_t>(std::forward<Rs>(ranges)...);
} else {
// If at least one input range has a dynamic extent, we must use a dynamically allocated output container.
// We assume that the user wants to use the first input range's container type as output type.
static_assert(
concepts::reservable_container<candidate_result_t>,
"First input range has static extent, but a dynamically allocated output range is required. Please explicitly specify a dynamically allocatable output type.");
return detail::concatenate<candidate_result_t>(std::forward<Rs>(ranges)...);
}
} else {
// The caller has explicitly specified the output type
return detail::concatenate<OutR>(std::forward<Rs>(ranges)...);
}
}
} // namespace Botan
#endif