Botan 3.11.0
Crypto and TLS for C&
concat_util.h
Go to the documentation of this file.
1/*
2* (C) 2023-2024 René Meusel - Rohde & Schwarz Cybersecurity
3*
4* Botan is released under the Simplified BSD License (see license.txt)
5*/
6
7#ifndef BOTAN_CONCAT_UTIL_H_
8#define BOTAN_CONCAT_UTIL_H_
9
10#include <botan/assert.h>
11#include <botan/concepts.h>
12#include <botan/range_concepts.h>
13#include <botan/strong_type.h>
14#include <array>
15#include <iterator>
16#include <ranges>
17#include <span>
18#include <tuple>
19
20namespace Botan {
21
22namespace detail {
23
24/**
25 * Helper function that performs range size-checks as required given the
26 * selected output and input range types. If all lengths are known at compile
27 * time, this check will be performed at compile time as well. It will then
28 * instantiate an output range and concatenate the input ranges' contents.
29 */
30template <ranges::spanable_range OutR, ranges::spanable_range... Rs>
31constexpr OutR concatenate(Rs&&... ranges)
33{
34 OutR result{};
35
36 // Prepare and validate the output range and construct a lambda that does the
37 // actual filling of the result buffer.
38 // (if no input ranges are given, GCC claims that fill_fn is unused)
39 [[maybe_unused]] auto fill_fn = [&] {
41 // dynamically allocate the correct result byte length
42 const size_t total_size = (ranges.size() + ... + 0);
43 result.reserve(total_size);
44
45 // fill the result buffer using a back-inserter
46 return [&result](auto&& range) {
47 std::copy(
48 std::ranges::begin(range), std::ranges::end(range), std::back_inserter(unwrap_strong_type(result)));
49 };
50 } else {
51 if constexpr((ranges::statically_spanable_range<Rs> && ... && true)) {
52 // all input ranges have a static extent, so check the total size at compile time
53 // (work around an issue in MSVC that warns `total_size` is unused)
54 [[maybe_unused]] constexpr size_t total_size = (decltype(std::span{ranges})::extent + ... + 0);
55 static_assert(result.size() == total_size, "size of result buffer does not match the sum of input buffers");
56 } else {
57 // at least one input range has a dynamic extent, so check the total size at runtime
58 const size_t total_size = (ranges.size() + ... + 0);
59 BOTAN_ARG_CHECK(result.size() == total_size,
60 "result buffer has static extent that does not match the sum of input buffers");
61 }
62
63 // fill the result buffer and hold the current output-iterator position
64 return [itr = std::ranges::begin(result)](auto&& range) mutable {
65 std::copy(std::ranges::begin(range), std::ranges::end(range), itr);
66 std::advance(itr, std::ranges::size(range));
67 };
68 }
69 }();
70
71 // perform the actual concatenation
72 (fill_fn(std::forward<Rs>(ranges)), ...);
73
74 return result;
75}
76
77} // namespace detail
78
79/**
80 * Concatenate an arbitrary number of buffers. Performs range-checks as needed.
81 *
82 * The output type can be auto-detected based on the input ranges, or explicitly
83 * specified by the caller. If all input ranges have a static extent, the total
84 * size is calculated at compile time and a statically sized std::array<> is used.
85 * Otherwise this tries to use the type of the first input range as output type.
86 *
87 * Alternatively, the output container type can be specified explicitly.
88 */
89template <typename OutR = detail::AutoDetect, ranges::spanable_range... Rs>
90constexpr auto concat(Rs&&... ranges)
91 requires(all_same_v<std::ranges::range_value_t<Rs>...>)
92{
93 if constexpr(std::same_as<detail::AutoDetect, OutR>) {
94 // Try to auto-detect a reasonable output type given the input ranges
95 static_assert(sizeof...(Rs) > 0, "Cannot auto-detect the output type if not a single input range is provided.");
96 using candidate_result_t = std::remove_cvref_t<std::tuple_element_t<0, std::tuple<Rs...>>>;
97 using result_range_value_t = std::remove_cvref_t<std::ranges::range_value_t<candidate_result_t>>;
98
99 if constexpr((ranges::statically_spanable_range<Rs> && ...)) {
100 // If all input ranges have a static extent, we can calculate the total size at compile time
101 // and therefore can use a statically sized output container. This is constexpr.
102 constexpr size_t total_size = (decltype(std::span{ranges})::extent + ... + 0);
103 using out_array_t = std::array<result_range_value_t, total_size>;
104 return detail::concatenate<out_array_t>(std::forward<Rs>(ranges)...);
105 } else {
106 // If at least one input range has a dynamic extent, we must use a dynamically allocated output container.
107 // We assume that the user wants to use the first input range's container type as output type.
108 static_assert(
110 "First input range has static extent, but a dynamically allocated output range is required. Please explicitly specify a dynamically allocatable output type.");
111 return detail::concatenate<candidate_result_t>(std::forward<Rs>(ranges)...);
112 }
113 } else {
114 // The caller has explicitly specified the output type
115 return detail::concatenate<OutR>(std::forward<Rs>(ranges)...);
116 }
117}
118
119} // namespace Botan
120
121#endif
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
constexpr OutR concatenate(Rs &&... ranges)
Definition concat_util.h:31
constexpr decltype(auto) unwrap_strong_type(T &&t)
Generically unwraps a strong type to its underlying type.
constexpr auto concat(Rs &&... ranges)
Definition concat_util.h:90