Line data Source code
1 : // Copyright (c) 2011-2023 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 : #pragma once
19 :
20 : /** \file
21 : * \brief Join strings together.
22 : *
23 : * This template expects a container of strings and a string separator.
24 : * It accumulates the strings by concatenating them one after the
25 : * other and adding the separator between each string. The separator is
26 : * not added at the start or the end of the resulting string.
27 : *
28 : * You can also specified a start and end from your container if you
29 : * do not want to join all the strings defined in your container.
30 : *
31 : * If you want to do concatenation at compile time, use the string_view
32 : * templates instead of an std::string container.
33 : */
34 :
35 : // C++
36 : //
37 : #include <algorithm>
38 : #include <array>
39 : #include <numeric>
40 : #include <string>
41 : #include <string_view>
42 :
43 :
44 :
45 : namespace snapdev
46 : {
47 :
48 : /** \brief Transform a set of strings in a string.
49 : *
50 : * This function concatenate all the strings from a container adding a
51 : * separator in between each. In effect, it does:
52 : *
53 : * \code
54 : * s1 + sep + s2 + sep + s3...
55 : * \endcode
56 : *
57 : * If you do not need a separator, you can use the std::accumulate() function
58 : * although it is going to be slower (i.e. it will do a lot of realloc() since
59 : * it does not know how long the final string is going to be.)
60 : *
61 : * \note
62 : * The separator is added whether the token being added is empty or not.
63 : * If you do not want to add separators between empty strings, make sure
64 : * to remove them from your container first.
65 : *
66 : * \param[in] tokens The container of strings.
67 : * \param[in] separator The separator to add between each string.
68 : *
69 : * \return the number of items in the resulting container.
70 : */
71 : template<class ContainerT>
72 19 : typename ContainerT::value_type join_strings(
73 : ContainerT const & tokens
74 : , typename ContainerT::value_type const & separator)
75 : {
76 19 : typename ContainerT::value_type result;
77 :
78 : // we have a special case because we want to access the first
79 : // item (see tokens[0] below) to make the for_each() simpler.
80 : //
81 19 : if(!tokens.empty())
82 : {
83 : // calculate the final size, which is way faster than reallocating
84 : // over and over again in the 'result += string' below
85 : //
86 32 : std::size_t const total_size(std::accumulate(
87 : tokens.begin()
88 : , tokens.end()
89 32 : , static_cast<std::string>(separator).length() * (tokens.size() - 1)
90 105 : , [](std::size_t const & sum, typename ContainerT::value_type const & str)
91 : {
92 105 : return sum + static_cast<std::string>(str).length();
93 : }));
94 :
95 16 : static_cast<std::string &>(result).reserve(total_size);
96 :
97 : // avoid special case in the loop
98 : // (i.e. no separator before the first token)
99 : //
100 16 : static_cast<std::string &>(result) += *tokens.begin();
101 :
102 16 : std::for_each(
103 : std::next(tokens.begin())
104 : , tokens.end()
105 89 : , [separator, &result](auto const & s)
106 : {
107 173 : static_cast<std::string &>(result) += static_cast<std::string const &>(separator)
108 84 : + static_cast<std::string const &>(s);
109 : });
110 : }
111 :
112 19 : return result;
113 0 : }
114 :
115 :
116 : template<class InputIt>
117 : typename std::iterator_traits<InputIt>::value_type join_strings(
118 : InputIt const & first
119 : , InputIt const & last
120 : , typename std::iterator_traits<InputIt>::value_type const & separator)
121 : {
122 : typename std::iterator_traits<InputIt>::value_type result;
123 :
124 : // we have a special case because we want to access the first
125 : // item (see tokens[0] below) to make the for_each() simpler.
126 : //
127 : if(first != last)
128 : {
129 : // calculate the final size, which is way faster than reallocating
130 : // over and over again in the 'result += string' below
131 : //
132 : std::size_t const total_size(std::accumulate(
133 : first
134 : , last
135 : , separator.length() * (std::distance(first, last) - 1)
136 : , [](std::size_t const & sum, typename std::iterator_traits<InputIt>::value_type const & str)
137 : {
138 : return sum + str.length();
139 : }));
140 :
141 : result.reserve(total_size);
142 :
143 : // avoid special case in the loop
144 : // (i.e. no separator before the first or after the last token)
145 : //
146 : result += *first;
147 :
148 : std::for_each(
149 : std::next(first)
150 : , last
151 : , [separator, &result](auto const & s)
152 : {
153 : result += separator + s;
154 : });
155 : }
156 :
157 : return result;
158 : }
159 :
160 :
161 : namespace detail
162 : {
163 :
164 : /** \brief Join strings at compile time.
165 : *
166 : * Here is a template to concatate a set of `std::string_view`'s.
167 : *
168 : * If you have two or more string views that you want to join at compile
169 : * time, the join_string_views template uses this template and returns
170 : * an std::string_view.
171 : */
172 : template<std::string_view const & ...strings>
173 : struct join_string_views_impl
174 : {
175 : // join all strings into a single std::array of chars
176 : //
177 : static constexpr auto concatenate() noexcept
178 : {
179 : constexpr std::size_t const len = (strings.size() + ... + 0);
180 : std::array<char, len + 1> arr{};
181 : auto append = [i = 0, &arr](auto const & s) mutable
182 : {
183 : for(auto c : s)
184 : {
185 : arr[i] = c;
186 : ++i;
187 : }
188 : };
189 : (append(strings), ...);
190 : arr[len] = 0;
191 : return arr;
192 : }
193 :
194 : // give the joined string static storage
195 : //
196 : static constexpr auto concatenated_strings = concatenate();
197 :
198 : // view as a std::string_view
199 : //
200 : static constexpr std::string_view value{
201 : concatenated_strings.data()
202 : , concatenated_strings.size() - 1
203 : };
204 : };
205 :
206 : /** \brief Join strings at compile time with a separator.
207 : *
208 : * This template is similar to the previous one, only it allows us to also
209 : * add a separator betweene each string.
210 : *
211 : * If you have two or more string views that you want to join with a
212 : * separator at compile time, the join_string_views_with_separator template
213 : * uses this template and returns an std::string_view.
214 : *
215 : * \tparam separator The separator to add between each string
216 : * \tparam strings The string views to concatanate
217 : */
218 : template<
219 : std::string_view const & separator
220 : , std::string_view const & ...strings>
221 : struct join_string_views_with_separator_impl
222 : {
223 : // join all strings into a single std::array of chars
224 : //
225 : static constexpr auto concatenate() noexcept
226 : {
227 : constexpr std::size_t const count = sizeof...(strings); //std::tuple_size<std::tuple<strings...>>::value;
228 : constexpr std::size_t const len = (strings.size() + ... + 0) + separator.length() * (count - 1);
229 : std::array<char, len + 1> arr{};
230 : auto append = [i = 0, &arr](auto const & s) mutable
231 : {
232 : if(i != 0)
233 : {
234 : for(auto c : separator)
235 : {
236 : arr[i] = c;
237 : ++i;
238 : }
239 : }
240 : for(auto c : s)
241 : {
242 : arr[i] = c;
243 : ++i;
244 : }
245 : };
246 : (append(strings), ...);
247 : arr[len] = 0;
248 : return arr;
249 : }
250 :
251 : // give the joined string static storage
252 : //
253 : static constexpr auto concatenated_strings = concatenate();
254 :
255 : // view as a std::string_view
256 : //
257 : static constexpr std::string_view value{
258 : concatenated_strings.data()
259 : , concatenated_strings.size() - 1
260 : };
261 : };
262 :
263 : } // namespace detail
264 :
265 :
266 : /** \brief Join string views at compile time.
267 : *
268 : * Whenever you create a string view (i.e. a string literal in C++17 and newer
269 : * compilers), you can concatenate them using the join_string_views template.
270 : *
271 : * Here is an example on how to use this template:
272 : *
273 : * \code
274 : * // various strings
275 : * //
276 : * constexpr std::string_view hello = "Hello";
277 : * constexpr std::string_view space = " ";
278 : * constexpr std::string_view world = "world";
279 : * constexpr std::string_view bang = "!";
280 : *
281 : * // concatenated strings
282 : * //
283 : * constexpr std::string_view hello_world = join_string_views<hello, space, world, bang>;
284 : * \endcode
285 : *
286 : * \source
287 : * https://stackoverflow.com/questions/38955940
288 : */
289 : template<std::string_view const & ...strings>
290 : static constexpr auto join_string_views = detail::join_string_views_impl<strings...>::value;
291 :
292 :
293 : /** \brief Join string views with a separator at compile time.
294 : *
295 : * Whenever you create a string view (i.e. a string literal in C++17 and newer
296 : * compilers), you can concatenate them using the
297 : * join_string_views_with_separator template.
298 : *
299 : * Here is an example on how to use this template:
300 : *
301 : * \code
302 : * // various strings
303 : * //
304 : * constexpr std::string_view space = " ";
305 : * constexpr std::string_view hello = "Hello";
306 : * constexpr std::string_view every = "every";
307 : * constexpr std::string_view body = "body";
308 : *
309 : * // concatenated strings
310 : * //
311 : * constexpr std::string_view hello_everyone = join_string_views<space, hello, every, body>;
312 : * \endcode
313 : */
314 : template<std::string_view const & separator, std::string_view const & ...strings>
315 : static constexpr auto join_string_views_with_separator = detail::join_string_views_with_separator_impl<separator, strings...>::value;
316 :
317 :
318 : } // namespace snapdev
319 : // vim: ts=4 sw=4 et
|