124

I'm looking for the most elegant way to implode a vector of strings into a string. Below is the solution I'm using now:

static std::string& implode(const std::vector<std::string>& elems, char delim, std::string& s)
{
    for (std::vector<std::string>::const_iterator ii = elems.begin(); ii != elems.end(); ++ii)
    {
        s += (*ii);
        if ( ii + 1 != elems.end() ) {
            s += delim;
        }
    }

    return s;
}

static std::string implode(const std::vector<std::string>& elems, char delim)
{
    std::string s;
    return implode(elems, delim, s);
}

Is there any others out there?

ezpresso
  • 7,896
  • 13
  • 62
  • 94
  • 1
    Why do you call this function implode? – Colonel Panic Apr 11 '19 at 20:33
  • 12
    @ColonelPanic, by analogy with PHP's implode() method, which joins array elements and outputs them as a single string. I wonder why are you asking this question:) – ezpresso Apr 13 '19 at 00:35
  • 7
    In Python: 'delim.join(elems)'. Sorry, could not resist. C++ still does not have batteries included. :-) Question is 10 years old in 2021 and not a single working _and_ elegant answer (trailing delimiters, excessive runtime, more #include lines that the naive implementation ...) – Johannes Overmann Nov 02 '21 at 17:51

22 Answers22

145

Use boost::algorithm::join(..):

#include <boost/algorithm/string/join.hpp>
...
std::string joinedString = boost::algorithm::join(elems, delim);

See also this question.

Community
  • 1
  • 1
Andre Holzner
  • 18,333
  • 6
  • 54
  • 63
  • 134
    Suggesting to include and link against the massive boost library to create a simple string is absurd. – Julian Oct 06 '17 at 21:24
  • 14
    @Julian most projects already do this. I agree that it is absurd that the STL does not include a way to do this already, however. I might also agree that this should not be the *top* answer, but other answers are clearly available. – River Tam Nov 24 '17 at 00:23
  • I concur with @Julian. Boost may be elegant in use but is no way the "most elegant way" in terms of overhead. In this case, it's a workaround to the OP's algorithm and not a resolution to the question itself. – Azeroth2b Nov 21 '18 at 13:49
  • 5
    Most Boost libraries are header-only, so there is nothing to link. Some even make their way into the standard. – jbruni Apr 27 '19 at 02:25
  • 19
    Not having this basic feature in stdlib is absurd. – Kiruahxh Mar 19 '21 at 07:11
  • @Kiruahxh [Proposed](http://www.open-std.org/jtc1/sc22/wg21/docs/papers//2013/n3594.html) in 2013, but appears to have [stalled](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/) for some reason. – Jason C May 04 '21 at 03:20
  • Can I get an answer without boost somewhere on planet. – Hitesh Kumar Saini Aug 14 '23 at 19:38
  • @HiteshKumarSaini just scroll down further, there are plenty of alternatives (including answers based on `std::ostringstream`, Abseil, Folly or specific to C++23) – Andre Holzner Aug 16 '23 at 12:15
44
std::vector<std::string> strings;

const char* const delim = ", ";

std::ostringstream imploded;
std::copy(strings.begin(), strings.end(),
           std::ostream_iterator<std::string>(imploded, delim));

(include <string>, <vector>, <sstream> and <iterator>)

If you want to have a clean end (no trailing delimiter) have a look here

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • 14
    keep in mind, though, that it will add extra delimiter (the second parameter to the `std::ostream_iterator` constructor at the end of the stream. – Michael Krelin - hacker Apr 16 '11 at 19:38
  • 29
    The point of "implode" is that a delimiter should not be added last. This answer unfortunately adds that delimiter last. – Jonny Oct 28 '15 at 01:13
  • And fortunately I need to add the token last as well! Thanks for the solution. – Константин Ван Dec 24 '20 at 01:57
  • I needed the actual string representation after the implode, so I used `imploded.str()` to get a `std::string`. Also, for those who don't want the delimiter as the last part of the string, use the `pop_back()` method to remove the last character after you convert to `std::string`. Thank you for the head start! – rayryeng Jan 16 '23 at 22:10
27

You should use std::ostringstream rather than std::string to build the output (then you can call its str() method at the end to get a string, so your interface need not change, only the temporary s).

From there, you could change to using std::ostream_iterator, like so:

copy(elems.begin(), elems.end(), ostream_iterator<string>(s, delim)); 

But this has two problems:

  1. delim now needs to be a const char*, rather than a single char. No big deal.
  2. std::ostream_iterator writes the delimiter after every single element, including the last. So you'd either need to erase the last one at the end, or write your own version of the iterator which doesn't have this annoyance. It'd be worth doing the latter if you have a lot of code that needs things like this; otherwise the whole mess might be best avoided (i.e. use ostringstream but not ostream_iterator).
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • 1
    Or use one that's already written: http://stackoverflow.com/questions/3496982/printing-lists-with-commas-c/3497021#3497021 – Jerry Coffin Apr 16 '11 at 20:03
27

I like to use this one-liner accumulate (no trailing delimiter):

(std::accumulate defined in <numeric>)

std::accumulate(
    std::next(elems.begin()), 
    elems.end(), 
    elems[0], 
    [](std::string a, std::string b) {
        return a + delimiter + b;
    }
);
user4581301
  • 33,082
  • 7
  • 33
  • 54
Sasa Milenkovic
  • 299
  • 3
  • 5
24

Because I love one-liners (they are very useful for all kinds of weird stuff, as you'll see at the end), here's a solution using std::accumulate and C++11 lambda:

std::accumulate(alist.begin(), alist.end(), std::string(), 
    [](const std::string& a, const std::string& b) -> std::string { 
        return a + (a.length() > 0 ? "," : "") + b; 
    } )

I find this syntax useful with stream operator, where I don't want to have all kinds of weird logic out of scope from the stream operation, just to do a simple string join. Consider for example this return statement from method that formats a string using stream operators (using std;):

return (dynamic_cast<ostringstream&>(ostringstream()
    << "List content: " << endl
    << std::accumulate(alist.begin(), alist.end(), std::string(), 
        [](const std::string& a, const std::string& b) -> std::string { 
            return a + (a.length() > 0 ? "," : "") + b; 
        } ) << endl
    << "Maybe some more stuff" << endl
    )).str();

Update:

As pointed out by @plexando in the comments, the above code suffers from misbehavior when the array starts with empty strings due to the fact that the check for "first run" is missing previous runs that have resulted in no additional characters, and also - it is weird to run a check for "is first run" on all runs (i.e. the code is under-optimized).

The solution for both of these problems is easy if we know for a fact that the list has at least one element. OTOH, if we know for a fact that the list does not have at least one element, then we can shorten the run even more.

I think the resulting code isn't as pretty, so I'm adding it here as The Correct Solution, but I think the discussion above still has merrit:

alist.empty() ? "" : /* leave early if there are no items in the list */
  std::accumulate( /* otherwise, accumulate */
    ++alist.begin(), alist.end(), /* the range 2nd to after-last */
    *alist.begin(), /* and start accumulating with the first item */
    [](auto& a, auto& b) { return a + "," + b; });

Notes:

  • For containers that support direct access to the first element, its probably better to use that for the third argument instead, so alist[0] for vectors.
  • As per the discussion in the comments and chat, the lambda still does some copying. This can be minimized by using this (less pretty) lambda instead: [](auto&& a, auto&& b) -> auto& { a += ','; a += b; return a; }) which (on GCC 10) improves performance by more than x10. Thanks to @Deduplicator for the suggestion. I'm still trying to figure out what is going on here.
Guss
  • 30,470
  • 17
  • 104
  • 128
  • 7
    Don't use `accumulate` for strings. Most of the other answers are O(n) but `accumulate` is O(n^2) because it makes a temporary copy of the accumulator before appending each element. And no, move semantics don't help. – Oktalist Sep 09 '13 at 20:31
  • 2
    @Oktalist, I'm not sure why you say that - http://www.cplusplus.com/reference/numeric/accumulate/ says "Complexity is linear in the distance between first and last". – Guss Sep 10 '13 at 06:43
  • 1
    That's assuming that each individual addition takes constant time. If `T` has an overloaded `operator+` (like `string` does) or if you provide your own functor then all bets are off. Although I may have been hasty in saying move semantics don't help, they don't solve the problem in the two implementations that I've checked. See my answers to [similar](http://stackoverflow.com/a/18703370/1639256) [questions](http://stackoverflow.com/a/18703743/1639256). – Oktalist Sep 10 '13 at 23:43
  • 1
    skwllsp's comment is nothing to do with it. Like I said, most of the other answers (and the OP's `implode` example) are doing the right thing. They are O(n), even if they don't call `reserve` on the string. Only the solution using accumulate is O(n^2). No need for C-style code. – Oktalist Sep 11 '13 at 14:32
  • 1
    I've reviewed all the answers as well as the OPs code sample, and all of them except for the one doing reserve (with a hard-coded value which we hope is large enough) are re-copying all the so far accumulated data on every iteration (std::string normally allocates more memory than it actually needs for appending, so appending delim will likely not incur another copy). The main difference between those implementations and std::accumulate based is the additional "return a copy" from the lambda, for which move semantics will prevent a large copy operation. – Guss Sep 11 '13 at 14:45
  • 1
    On _every_ iteration? So you're saying they're _all_ O(n^2)? Not on my machine. They don't copy on every iteration for the same reason that they don't copy when appending delim, `string` append is usually amortized O(1) because it allocates _much_ more than it needs. Here's a quick benchmark: http://codepad.org/mfubiiMg – Oktalist Sep 11 '13 at 18:39
  • 14
    I did a [benchmark](https://gist.github.com/kirbyfan64/e69b605a287cedcd57f5), and accumulate was actually faster than an O(n) string stream. – kirbyfan64sos Mar 06 '15 at 22:22
  • 1
    A questionable algorithmic design. You *know* upfront that a comma needs to be prepended to every element except the first one, then why do you do all those useless runtime checks? In addition to that, your code skips all empty pieces until the first non-empty one, then thereafter, empty pieces are not skipped anymore but appended. – plexando Oct 29 '20 at 08:49
  • @plexando - **you are absolutely correct**. I wasn't sure about the first part of your comment, but as I checked out the skipping empty strings issue I figured out a solution that solves both problems. The result, while more resilient, is less pretty so I'll just add it to the answer as an update. Thank you for pointing at the problem! ‍♂️ – Guss Oct 29 '20 at 10:48
  • The last one is bad: It doesn't take advantage of [`std::move()` in `std::accumulate()`](https://en.cppreference.com/w/cpp/algorithm/accumulate), resulting in at least one allocation per element added, more likely two. The lambda should be changed to `[](auto&& a, auto&& b){ a += ","; a += b; return decltype(a)(a); }`. That is, if you don't want to change it all to `std::accumulate(alist.begin(), alist.end(), std::string(), [bool f = false](auto&& a, auto&& b) mutable { if (f) a += ","; f = true; a += b; return decltype(a)(a); })` and let the compiler lower the flag to code. – Deduplicator Oct 29 '20 at 11:57
  • @Deduplicator Why do you think `return a + "," + b;` doesn't use move semantics (while my previous code did) and will changing it to `return (a += ",") += b;` help? – Guss Oct 29 '20 at 12:04
  • It doesn't matter whether `return a + "," + b;` uses move-semantics for the return itself, as it must perforce create a new object divorced from both `a` and `b` from scratch, as neither are xvalues there. Actually, the temporary is subject to RVO. Using op+= doesn't help, [as that isn't overloaded for xvalue this](https://en.cppreference.com/w/cpp/string/basic_string/operator%2B%3D). – Deduplicator Oct 29 '20 at 12:12
  • @Deduplicator, after reading https://en.cppreference.com/w/cpp/algorithm/accumulate, I'm still not sure I understand how you can tell when the lambda return value can be moved or not, but I'm pretty sure their examples mean that `a` is an *xvalue*. I did ran some benchmarks (w/ gcc 10) and while your suggestions of using forwarding references, `mutable`, `decltype` or the captured `bool` have no effect, using two `+=` statements instead of three add terms does improve performance by ~20% (!!). Interestingly, so does just writing `return a + ("," + b);` - so I guess we know what the problem is. – Guss Oct 29 '20 at 14:16
  • Have you tried making the arguments forward-references and using the first as `decltype(a)(a)`? – Deduplicator Oct 29 '20 at 15:13
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/223826/discussion-between-guss-and-deduplicator). – Guss Oct 29 '20 at 15:18
  • @kirbyfan64sos was the gist removed? – Moberg Jun 16 '22 at 08:02
  • 1
    @Moberg I changed my github username, new URL is https://gist.github.com/refi64/e69b605a287cedcd57f5 – kirbyfan64sos Jun 22 '22 at 20:36
20

what about simple stupid solution?

std::string String::join(const std::vector<std::string> &lst, const std::string &delim)
{
    std::string ret;
    for(const auto &s : lst) {
        if(!ret.empty())
            ret += delim;
        ret += s;
    }
    return ret;
}
user3545770
  • 337
  • 2
  • 3
  • I hope the compiler is smart enough to remove the check for the `ret` to be empty on _every_ iteration. – xtofl May 05 '21 at 06:50
  • 2
    @xtofl You're overestimating the cost of that check. – c z Jan 25 '22 at 16:06
  • 1
    I'm just following the "Don't pay for what you don't use" rule. You're right in most cases, where `lst` is not enormous. – xtofl Jan 26 '22 at 11:25
  • 2
    Besides the fact that `ret.empty()` is a trivial check, this is a use case that is perfect for branch predictors, since after the first test it will always evaluate to false. – Bruce Nielsen Apr 25 '22 at 08:25
15

With fmt you can do.

#include <fmt/format.h>
auto s = fmt::format("{}",fmt::join(elems,delim)); 

But I don't know if join will make it to std::format.

andreas777
  • 173
  • 1
  • 5
  • At least not in C++20. – Franklin Yu Jan 27 '23 at 02:32
  • @FranklinYu He is talking about [this fmt library](https://github.com/fmtlib/fmt), not a standard C++ library – perrocallcenter Mar 01 '23 at 22:48
  • @perrocallcenter I know that. My comment above was referring to the part “*if join will make it to `std::format`*”, which was referring to the fact that a big portion of the `fmt` library has been added to C++20 (i.e. the `std::format`), but for some reason `fmt::join()` was left out. “Just use the fmt library” is a simple fix, but it might not be feasible for every team. It would still be useful to have something similar to `fmt::join()` added to ``. – Franklin Yu Mar 03 '23 at 05:24
  • @perrocallcenter as FranklinY said, std::format (https://en.cppreference.com/w/cpp/header/format) and std::print (https://en.cppreference.com/w/cpp/io/print, C++ 23) are based on fmt library. I was hoping that they also standardize join, but it does no seems to be the case. It seems they only standardized join for views (https://en.cppreference.com/w/cpp/23). – andreas777 Mar 03 '23 at 07:34
12
string join(const vector<string>& vec, const char* delim)
{
    stringstream res;
    copy(vec.begin(), vec.end(), ostream_iterator<string>(res, delim));
    return res.str();
}
Artless
  • 4,522
  • 1
  • 25
  • 40
BlackMamba
  • 10,054
  • 7
  • 44
  • 67
9

Especially with bigger collections, you want to avoid having to check if youre still adding the first element or not to ensure no trailing separator...

So for the empty or single-element list, there is no iteration at all.

Empty ranges are trivial: return "".

Single element or multi-element can be handled perfectly by accumulate:

auto join = [](const auto &&range, const auto separator) {
    if (range.empty()) return std::string();

    return std::accumulate(
         next(begin(range)), // there is at least 1 element, so OK.
         end(range),

         range[0], // the initial value

         [&separator](auto result, const auto &value) {
             return result + separator + value;
         });
};

Running sample (require C++14): http://cpp.sh/8uspd

Petr B.
  • 879
  • 1
  • 8
  • 17
xtofl
  • 40,723
  • 12
  • 105
  • 192
  • You'd never need to check every time though. Just add the first element outside the loop, and start the loop at the second... – Jason C May 04 '21 at 03:07
  • I don't see why you'd add that. There isn't a loop in this function, and `accumulate` _does_ receive the first element and is told to start at the second... – xtofl May 05 '21 at 06:54
  • 1
    What I mean is: *"Especially with bigger collections, you want to avoid having to check if youre still adding the first element or not to ensure no trailing separator."* -- You can avoid having to check this in the loop method that your statement refers to by pulling the first element out of the loop. Sorry, I was kinda vague; I was commenting on the premise, not on the solution. The *solution* you provided is perfectly fine. – Jason C May 05 '21 at 15:14
  • I share your idea. Related: https://stackoverflow.com/questions/156650/does-the-last-element-in-a-loop-deserve-a-separate-treatment. – xtofl May 06 '21 at 05:48
5

While I would normally recommend using Boost as per the top answer, I recognise that in some projects that's not desired.

The STL solutions suggested using std::ostream_iterator will not work as intended - it'll append a delimiter at the end.

There is now a way to do this with modern C++ using std::experimental::ostream_joiner:

std::ostringstream outstream;
std::copy(strings.begin(),
          strings.end(),
          std::experimental::make_ostream_joiner(outstream, delimiter.c_str()));
return outstream.str();
Zitrax
  • 19,036
  • 20
  • 88
  • 110
Riot
  • 15,723
  • 4
  • 60
  • 67
  • How modern does C++ need to be? I tried this with `set(CMAKE_CXX_STANDARD 11)` and `#include `, and got "`error: 'std::experimental' has not been declared`" – sdbbs Jun 05 '23 at 09:32
  • Your compiler needs to support [library fundamentals TS v2](https://en.cppreference.com/w/cpp/experimental/lib_extensions_2). At the time of writing, parts of this have been merged into C++17 and C++20, but `ostream_joiner` is not yet part of any modern standard up to and including C++23. I know it's supported by recent versions of GCC at the very least - you may need to build with `-std=gnu++2b`. – Riot Jun 06 '23 at 10:07
4

A version that uses std::accumulate:

#include <numeric>
#include <iostream>
#include <string>

struct infix {
  std::string sep;
  infix(const std::string& sep) : sep(sep) {}
  std::string operator()(const std::string& lhs, const std::string& rhs) {
    std::string rz(lhs);
    if(!lhs.empty() && !rhs.empty())
      rz += sep;
    rz += rhs;
    return rz;
  }
};

int main() {
  std::string a[] = { "Hello", "World", "is", "a", "program" };
  std::string sum = std::accumulate(a, a+5, std::string(), infix(", "));
  std::cout << sum << "\n";
}
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
3

Here is another one that doesn't add the delimiter after the last element:

std::string concat_strings(const std::vector<std::string> &elements,
                           const std::string &separator)
{       
    if (!elements.empty())
    {
        std::stringstream ss;
        auto it = elements.cbegin();
        while (true)
        {
            ss << *it++;
            if (it != elements.cend())
                ss << separator;
            else
                return ss.str();
        }       
    }
    return "";
Simón
  • 456
  • 8
  • 23
Darien Pardinas
  • 5,910
  • 1
  • 41
  • 48
3

A possible solution with ternary operator ?:.

std::string join(const std::vector<std::string> & v, const std::string & delimiter = ", ") {
    std::string result;

    for (size_t i = 0; i < v.size(); ++i) {
        result += (i ? delimiter : "") + v[i]; 
    }

    return result;
}

join({"2", "4", "5"}) will give you 2, 4, 5.

Ynjxsjmh
  • 28,441
  • 6
  • 34
  • 52
3

Another simple and good solution is using ranges v3. The current version is C++14 or greater, but there are older versions that are C++11 or greater. Unfortunately, C++20 ranges don't have the intersperse function.

The benefits of this approach are:

  • Elegant
  • Easily handle empty strings
  • Handles the last element of the list
  • Efficiency. Because ranges are lazily evaluated.
  • Small and useful library

Functions breakdown(Reference):

  • accumulate = Similar to std::accumulate but arguments are a range and the initial value. There is an optional third argument that is the operator function.
  • filter = Like std::filter, filter the elements that don't fit the predicate.
  • intersperse = The key function! Intersperses a delimiter between range input elements.
#include <iostream>
#include <string>
#include <vector>
#include <range/v3/numeric/accumulate.hpp>
#include <range/v3/view/filter.hpp>
#include <range/v3/view/intersperse.hpp>

int main()
{
    using namespace ranges;
    // Can be any std container
    std::vector<std::string> a{ "Hello", "", "World", "is", "", "a", "program" };
    
    std::string delimiter{", "};
    std::string finalString = 
        accumulate(a | views::filter([](std::string s){return !s.empty();})
                     | views::intersperse(delimiter)
                  , std::string());
    std::cout << finalString << std::endl; // Hello, World, is, a, program
}

Edit: As @Franklin Yu suggested, it's possible only use the std libraries with std::ranges::views::join_with. But unfortunately is only available for c++23. Since we are using c++23 we also can use std::ranges::fold_left instead std::accumulate to create a one line expression. std::ranges::fold_left is the std version of rages v3's rages::accumulate.

#include <iostream>
#include <string>
#include <vector>
#include <ranges>
#include <algorithm>

int main()
{
    // Can be any std container
    std::vector<std::string> a{ "Hello", "", "World", "is", "", "a", "program" };
    
    std::string delimiter{", "};
    
    std::string finalString = 
        std::ranges::fold_left(a | std::views::filter([](std::string s){return !s.empty();})
                                 | std::views::join_with(delimiter)
                              , std::string()
                              , std::plus());
 
    std::cout << finalString << std::endl; // Hello, World, is, a, program
}
Augusto Icaro
  • 553
  • 5
  • 15
  • 2
    C++20 do have [`std::ranges::views::join_with`](https://en.cppreference.com/w/cpp/ranges/join_with_view), which might help? – Franklin Yu Sep 08 '22 at 15:30
  • @FranklinYu. Yes `std::ranges::views::join_with` it's the equivalent function but is only available for c++23. – Augusto Icaro Aug 15 '23 at 11:13
2

Here's what I use, simple and flexible

string joinList(vector<string> arr, string delimiter)
{
    if (arr.empty()) return "";

    string str;
    for (auto i : arr)
        str += i + delimiter;
    str = str.substr(0, str.size() - delimiter.size());
    return str;
}

using:

string a = joinList({ "a", "bbb", "c" }, "!@#");

output:

a!@#bbb!@#c
user1438233
  • 1,153
  • 1
  • 14
  • 30
2

Using part of this answer to another question gives you a joined this, based on a separator without a trailing comma,

Usage:

std::vector<std::string> input_str = std::vector<std::string>({"a", "b", "c"});
std::string result = string_join(input_str, ",");
printf("%s", result.c_str());
/// a,b,c

Code:

std::string string_join(const std::vector<std::string>& elements, const char* const separator)
{
    switch (elements.size())
    {
        case 0:
            return "";
        case 1:
            return elements[0];
        default:
            std::ostringstream os;
            std::copy(elements.begin(), elements.end() - 1, std::ostream_iterator<std::string>(os, separator));
            os << *elements.rbegin();
            return os.str();
    }
}
TankorSmash
  • 12,186
  • 6
  • 68
  • 106
2

If you are already using a C++ base library (for commonly used tools), string-processing features are typically included. Besides Boost mentioned above, Abseil provides:

std::vector<std::string> names {"Linus", "Dennis", "Ken"};
std::cout << absl::StrJoin(names, ", ") << std::endl;

Folly provides:

std::vector<std::string> names {"Linus", "Dennis", "Ken"};
std::cout << folly::join(", ", names) << std::endl;

Both give the string "Linus, Dennis, Ken".

Franklin Yu
  • 8,920
  • 6
  • 43
  • 57
2

This gets a handy one-liner in C++23:

auto str = std::ranges::fold_left(elems | std::views::join_with(delim), std::string{}, std::plus<>{});
Carsten
  • 11,287
  • 7
  • 39
  • 62
0

Slightly long solution, but doesn't use std::ostringstream, and doesn't require a hack to remove the last delimiter.

http://www.ideone.com/hW1M9

And the code:

struct appender
{
  appender(char d, std::string& sd, int ic) : delim(d), dest(sd), count(ic)
  {
    dest.reserve(2048);
  }

  void operator()(std::string const& copy)
  {
    dest.append(copy);
    if (--count)
      dest.append(1, delim);
  }

  char delim;
  mutable std::string& dest;
  mutable int count;
};

void implode(const std::vector<std::string>& elems, char delim, std::string& s)
{
  std::for_each(elems.begin(), elems.end(), appender(delim, s, elems.size()));
}
Nim
  • 33,299
  • 2
  • 62
  • 101
0

This can be solved using boost

#include <boost/range/adaptor/filtered.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/algorithm.hpp>

std::vector<std::string> win {"Stack", "", "Overflow"};
const std::string Delimitor{","};

const std::string combined_string = 
                  boost::algorithm::join(win |
                         boost::adaptors::filtered([](const auto &x) {
                                                      return x.size() != 0;
                                                      }), Delimitor);

Output:

combined_string: "Stack,Overflow"
0

I'm using the following approach that works fine in C++17. The function starts checking if the given vector is empty, in which case returns an empty string. If that's not the case, it takes the first element from the vector, then iterates from the second one until the end and appends the separator followed by the vector element.

template <typename T>
std::basic_string<T> Join(std::vector<std::basic_string<T>> vValues,
   std::basic_string<T> strDelim)
{
   std::basic_string<T> strRet;
   typename std::vector<std::basic_string<T>>::iterator it(vValues.begin());
   if (it != vValues.end())  // The vector is not empty
   {
      strRet = *it;
      while (++it != vValues.end()) strRet += strDelim + *it;
   }
   return strRet;
}

Usage example:

std::vector<std::string> v1;
std::vector<std::string> v2 { "Hello" };
std::vector<std::string> v3 { "Str1", "Str2" };
std::cout << "(1): " << Join<char>(v1, ",") << std::endl;
std::cout << "(2): " << Join<char>(v2, "; ") << std::endl;
std::cout << "(3): [" << Join<char>(v3, "] [") << "]" << std::endl;

Output:

(1): 
(2): Hello
(3): [Str1] [Str2]
alberto
  • 11
  • 1
  • 2
  • 4
0

Another std::accumulate solution, as a function, templated, along with error trapping.

Downsides, unsuitable for large sets of strings (potentially O(n^2)), and ugly usage:

join<std::vector<std::string>>(myVec.begin(), myVec.end(), ' ')

Here it is:

template<class T>
std::string join(char delimiter, 
                 typename T::iterator begin,
                 typename T::iterator end) {

    if(begin == end) {
        return std::string();
    }
    if(std::next(begin) == end) {
        return std::string(*begin);
    }
    return std::accumulate(std::next(begin),
                           end, 
                           *begin,
                           [delimiter](const std::string a, 
                                       const std::string b) -> std::string 
                                       {return a + delimiter + b;});
}

Example:

#include <iostream>
#include <numeric>
#include <string>
#include <vector>

// paste join template code here....

int main() {
    std::vector<std::string> vec { "One", "Two", "Three" };
    
    std::cout << "0: " << join<std::vector<std::string>>(' ',vec.begin()+0, vec.end());
    std::cout << std::endl;
    std::cout << "1: " << join<std::vector<std::string>>(' ',vec.begin()+1, vec.end());
    std::cout << std::endl;
    std::cout << "2: " << join<std::vector<std::string>>(' ',vec.begin()+2, vec.end());
    std::cout << std::endl;
    std::cout << "3: " << join<std::vector<std::string>>(' ',vec.begin()+3, vec.end());
    std::cout << std::endl;

    return 0;
}

results in:

0: One Two Three
1: Two Three
2: Three
3: 
oPless
  • 618
  • 1
  • 8
  • 18