LCOV - code coverage report
Current view: top level - snapdev - join_strings.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 14 15 93.3 %
Date: 2023-05-29 16:11:08 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.14