Line data Source code
1 : // Copyright (c) 2011-2022 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 2 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 along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 : #pragma once
20 :
21 : // C++
22 : //
23 : #include <algorithm>
24 : #include <array>
25 : #include <numeric>
26 : #include <string>
27 : #include <string_view>
28 :
29 :
30 :
31 : namespace snapdev
32 : {
33 :
34 : /** \brief Transform a set of strings in a string.
35 : *
36 : * This function concatenate all the strings from a container adding a
37 : * separator in between each. In effect, it does:
38 : *
39 : * \code
40 : * s1 + sep + s2 + sep + s3...
41 : * \endcode
42 : *
43 : * If you do not need a separator, you can use the std::accumulate() function
44 : * although it is going to be slower (i.e. it will do a lot of realloc() since
45 : * it does not know how long the final string is going to be.)
46 : *
47 : * \note
48 : * The separator is added whether the token being added is empty or not.
49 : * If you do not want to add separators between empty strings, make sure
50 : * to remove them from your container first.
51 : *
52 : * \param[in] tokens The container of strings.
53 : * \param[in] separator The separator to add between each string.
54 : *
55 : * \return the number of items in the resulting container.
56 : */
57 : template<class ContainerT>
58 4 : typename ContainerT::value_type join_strings(
59 : ContainerT const & tokens
60 : , typename ContainerT::value_type const & separator)
61 : {
62 4 : typename ContainerT::value_type result;
63 :
64 : // we have a special case because we want to access the first
65 : // item (see tokens[0] below) to make the for_each() simpler.
66 : //
67 4 : if(!tokens.empty())
68 : {
69 : // calculate the final size, which is way faster than reallocating
70 : // over and over again in the 'result += string' below
71 : //
72 3 : size_t const total_size(std::accumulate(tokens.begin(), tokens.end(), separator.length() * (tokens.size() - 1),
73 7 : [](size_t & sum, typename ContainerT::value_type const & str)
74 : {
75 7 : return sum + str.length();
76 7 : }));
77 :
78 3 : result.reserve(total_size);
79 :
80 : // avoid special case in the loop
81 : // (i.e. no separator before the first token)
82 : //
83 3 : result += *tokens.begin();
84 :
85 3 : std::for_each(
86 : std::next(tokens.begin())
87 : , tokens.end()
88 4 : , [&separator, &result](auto const & s)
89 8 : {
90 12 : result += separator + s;
91 4 : });
92 : }
93 :
94 4 : return result;
95 : }
96 :
97 :
98 : namespace detail
99 : {
100 :
101 : /** \brief Join strings at compile time.
102 : *
103 : * Here is a template to concatate a set of `std::string_view`'s.
104 : *
105 : * If you have two or more string views that you want to join at compile
106 : * time, the join_string_views template uses this template and returns
107 : * an std::string_view.
108 : */
109 : template<std::string_view const & ...strings>
110 : struct join_string_views_impl
111 : {
112 : // join all strings into a single std::array of chars
113 : //
114 : static constexpr auto concatenate() noexcept
115 : {
116 : constexpr std::size_t const len = (strings.size() + ... + 0);
117 : std::array<char, len + 1> arr{};
118 : auto append = [i = 0, &arr](auto const & s) mutable
119 : {
120 : for(auto c : s)
121 : {
122 : arr[i] = c;
123 : ++i;
124 : }
125 : };
126 : (append(strings), ...);
127 : arr[len] = 0;
128 : return arr;
129 : }
130 :
131 : // give the joined string static storage
132 : //
133 : static constexpr auto concatenated_strings = concatenate();
134 :
135 : // view as a std::string_view
136 : //
137 : static constexpr std::string_view value{
138 : concatenated_strings.data()
139 : , concatenated_strings.size() - 1
140 : };
141 : };
142 :
143 : /** \brief Join strings at compile time with a separator.
144 : *
145 : * This template is similar to the previous one, only it allows us to also
146 : * add a separator betweene each string.
147 : *
148 : * If you have two or more string views that you want to join with a
149 : * separator at compile time, the join_string_views_with_separator template
150 : * uses this template and returns an std::string_view.
151 : *
152 : * \tparam separator The separator to add between each string
153 : * \tparam strings The string views to concatanate
154 : */
155 : template<
156 : std::string_view const & separator
157 : , std::string_view const & ...strings>
158 : struct join_string_views_with_separator_impl
159 : {
160 : // join all strings into a single std::array of chars
161 : //
162 : static constexpr auto concatenate() noexcept
163 : {
164 : constexpr std::size_t const count = sizeof...(strings); //std::tuple_size<std::tuple<strings...>>::value;
165 : constexpr std::size_t const len = (strings.size() + ... + 0) + separator.length() * (count - 1);
166 : std::array<char, len + 1> arr{};
167 : auto append = [i = 0, &arr](auto const & s) mutable
168 : {
169 : if(i != 0)
170 : {
171 : for(auto c : separator)
172 : {
173 : arr[i] = c;
174 : ++i;
175 : }
176 : }
177 : for(auto c : s)
178 : {
179 : arr[i] = c;
180 : ++i;
181 : }
182 : };
183 : (append(strings), ...);
184 : arr[len] = 0;
185 : return arr;
186 : }
187 :
188 : // give the joined string static storage
189 : //
190 : static constexpr auto concatenated_strings = concatenate();
191 :
192 : // view as a std::string_view
193 : //
194 : static constexpr std::string_view value{
195 : concatenated_strings.data()
196 : , concatenated_strings.size() - 1
197 : };
198 : };
199 :
200 : } // namespace detail
201 :
202 :
203 : /** \brief Join string views at compile time.
204 : *
205 : * Whenever you create a string view (i.e. a string literal in C++17 and newer
206 : * compilers), you can concatenate them using the join_string_views template.
207 : *
208 : * Here is an example on how to use this template:
209 : *
210 : * \code
211 : * // various strings
212 : * //
213 : * constexpr std::string_view hello = "Hello";
214 : * constexpr std::string_view space = " ";
215 : * constexpr std::string_view world = "world";
216 : * constexpr std::string_view bang = "!";
217 : *
218 : * // concatenated strings
219 : * //
220 : * constexpr std::string_view hello_world = join_string_views<hello, space, world, bang>;
221 : * \endcode
222 : *
223 : * \source
224 : * https://stackoverflow.com/questions/38955940
225 : */
226 : template<std::string_view const & ...strings>
227 : static constexpr auto join_string_views = detail::join_string_views_impl<strings...>::value;
228 :
229 :
230 : /** \brief Join string views with a separator at compile time.
231 : *
232 : * Whenever you create a string view (i.e. a string literal in C++17 and newer
233 : * compilers), you can concatenate them using the
234 : * join_string_views_with_separator template.
235 : *
236 : * Here is an example on how to use this template:
237 : *
238 : * \code
239 : * // various strings
240 : * //
241 : * constexpr std::string_view space = " ";
242 : * constexpr std::string_view hello = "Hello";
243 : * constexpr std::string_view every = "every";
244 : * constexpr std::string_view body = "body";
245 : *
246 : * // concatenated strings
247 : * //
248 : * constexpr std::string_view hello_everyone = join_string_views<space, hello, every, body>;
249 : * \endcode
250 : */
251 : template<std::string_view const & separator, std::string_view const & ...strings>
252 : static constexpr auto join_string_views_with_separator = detail::join_string_views_with_separator_impl<separator, strings...>::value;
253 :
254 :
255 : } // namespace snapdev
256 : // vim: ts=4 sw=4 et
|