/* * (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 #include #include #include #include #include #include #include #include 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 constexpr OutR concatenate(Rs&&... ranges) requires(concepts::reservable_container || ranges::statically_spanable_range) { 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) { // 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 && ... && 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(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 constexpr auto concat(Rs&&... ranges) requires(all_same_v...>) { if constexpr(std::same_as) { // 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>>; using result_range_value_t = std::remove_cvref_t>; if constexpr((ranges::statically_spanable_range && ...)) { // 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; return detail::concatenate(std::forward(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, "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(std::forward(ranges)...); } } else { // The caller has explicitly specified the output type return detail::concatenate(std::forward(ranges)...); } } } // namespace Botan #endif