55

I would like to copy the contents of a vector to one long string with a custom delimiter. So far, I've tried:

// .h
string getLabeledPointsString(const string delimiter=",");
// .cpp
string Gesture::getLabeledPointsString(const string delimiter) {
    vector<int> x = getLabeledPoints();
    stringstream  s;
    copy(x.begin(),x.end(), ostream_iterator<int>(s,delimiter));
    return s.str();
}

but I get

no matching function for call to ‘std::ostream_iterator<int, char, std::char_traits<char> >::ostream_iterator(std::stringstream&, const std::string&)’

I've tried with charT* but I get

error iso c++ forbids declaration of charT with no type

Then I tried using char and ostream_iterator<int>(s,&delimiter) but I get strange characters in the string.

Can anyone help me make sense of what the compiler is expecting here?

2
  • yes wouldn't it be nice if the compiler kindly told you what type it was expecting. Incidentally you will get a comma after your last element too. Commented Feb 14, 2012 at 13:54
  • 1
    The most elegant way is to use boost::algorithm::join() for this as described in stackoverflow.com/a/6334153/2056686 Commented Nov 23, 2016 at 12:55

15 Answers 15

36

Use delimiter.c_str() as the delimiter:

copy(x.begin(),x.end(), ostream_iterator<int>(s,delimiter.c_str()));

That way, you get a const char* pointing to the string, which is what ostream_operator expects from your std::string.

Sign up to request clarification or add additional context in comments.

3 Comments

+1, but also note that this will write a trailing delimiter in the output.
I'm not sure about the performance here. A stringstream manages its own buffer, so it must grow dynamically. Here you know before generating the string what length it's going to be, so you should reserve the buffer before concatenating.
"you know before generating the string what length it's going to be" - or at any rate you know an upper bound. If stringstream grows its buffer exponentially then I wouldn't worry about performance, but I don't know whether that's the case.
27

C++11:

vector<string> x = {"1", "2", "3"};
string s = std::accumulate(std::begin(x), std::end(x), string(),
                                [](string &ss, string &s)
                                {
                                    return ss.empty() ? s : ss + "," + s;
                                });

10 Comments

Looks neat, but wouldn't this create many strings in the process? Any way to improve this using string stream?
Sounds reasonable, but that's not what the code above does. It's not appending - it's generating new strings.
@xtofl that will fail for an empty vector where begin == end
any reason you are passing non-const references?
great answer - especially if you make it a constexpr function; then all the performance issues everyone raises go away
|
13

Another way to do it:

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
using namespace std;

template <typename T>
string join(const T& v, const string& delim) {
    ostringstream s;
    for (const auto& i : v) {
        if (&i != &v[0]) {
            s << delim;
        }
        s << i;
    }
    return s.str();
}

int main() {
    cout << join(vector<int>({1, 2, 3, 4, 5}), ",") << endl;
}

(c++11 range-based for loop and 'auto' though)

Comments

13

This is an extension to the two answers already provided above as run-time performance seemed to be a theme in the comments. I would have added it as comments, but I do not have that privilege yet.

I tested 2 implementations for run-time performance using Visual Studio 2015:

Using stringstream:

std::stringstream result;
auto it = vec.begin();
result << (unsigned short)*it++;
for (; it != vec.end(); it++) {
    result << delimiter;
    result << (unsigned short)*it;
}
return result.str();

Using accumulate:

std::string result = std::accumulate(std::next(vec.begin()), vec.end(),
    std::to_string(vec[0]),
    [&delimiter](std::string& a, uint8_t b) {
    return a + delimiter+ std::to_string(b);
});
return result;

Release build run-time performance was close with a couple subtleties.

The accumulate implementation was slightly faster (20-50ms, ~10-30% of the overall run-time (~180ms) on 1000 iterations over a 256 element vector). However, the accumulate implementation was only faster when the a parameter to the lambda function was passed by reference. Passing the a parameter by value resulted in a similar run-time difference favoring the stringstream implementation. The accumulate implementation also improved some when the result string was returned directly rather than assigned to a local variable that was immediately returned. YMMV with other C++ compilers.

The Debug build was 5-10 times slower using accumulate so I think the extra string creation noted in several comments above is resolved by the optimizer.

I was looking at a specific implementation using a vector of uint8_t values. The full test code follows:

#include <vector>
#include <iostream>
#include <sstream>
#include <numeric>
#include <chrono>

using namespace std;
typedef vector<uint8_t> uint8_vec_t;

string concat_stream(const uint8_vec_t& vec, string& delim = string(" "));
string concat_accumulate(const uint8_vec_t& vec, string& delim = string(" "));

string concat_stream(const uint8_vec_t& vec, string& delimiter)
{
    stringstream result;

    auto it = vec.begin();
    result << (unsigned short)*it++;
    for (; it != vec.end(); it++) {
        result << delimiter;
        result << (unsigned short)*it;
    }
    return result.str();
}

string concat_accumulate(const uint8_vec_t& vec, string& delimiter)
{
    return accumulate(next(vec.begin()), vec.end(),
        to_string(vec[0]),
        [&delimiter](string& a, uint8_t b) {
        return a + delimiter + to_string(b);
    });
}

int main()
{
    const int elements(256);
    const int iterations(1000);

    uint8_vec_t test(elements);
    iota(test.begin(), test.end(), 0);

    int i;
    auto stream_start = chrono::steady_clock::now();
    string join_with_stream;
    for (i = 0; i < iterations; ++i) {
        join_with_stream = concat_stream(test);
    }
    auto stream_end = chrono::steady_clock::now();

    auto acc_start = chrono::steady_clock::now();
    string join_with_acc;
    for (i = 0; i < iterations; ++i) {
        join_with_acc = concat_accumulate(test);
    }
    auto acc_end = chrono::steady_clock::now();

    cout << "Stream Results:" << endl;
    cout << "    elements: " << elements << endl;
    cout << "    iterations: " << iterations << endl;
    cout << "    runtime: " << chrono::duration<double, milli>(stream_end - stream_start).count() << " ms" << endl;
    cout << "    result: " << join_with_stream << endl;

    cout << "Accumulate Results:" << endl;
    cout << "    elements: " << elements << endl;
    cout << "    iterations: " << iterations << endl;
    cout << "    runtime: " << chrono::duration<double, milli>(acc_end - acc_start).count() << " ms" << endl;
    cout << "    result:" << join_with_acc << endl;

    return 0;
}

2 Comments

I love that you provided performance numbers -- very useful for making a decision one way or the other, thanks!
Just to state the obvious: concat_stream() and concat_accumulate() only work for non empty vectors.
10
std::string Gesture::getLabeledPointsString(const std::string delimiter) {
  return boost::join(getLabeledPoints(), delimiter);
}

I am not that convinced about introducting getLabeledPointsString at this point ;)

2 Comments

ehehehheheh is there a cpp without boost? +1 for boost, thanks!
@nkint: Oh you can certainly program without boost. But that's about as difficult as Python without its libraries: you just need to create all the tools by yourself ;)
5
string join(const vector<string> & v, const string & delimiter = ",") {
    string out;
    if (auto i = v.begin(), e = v.end(); i != e) {
        out += *i++;
        for (; i != e; ++i) out.append(delimiter).append(*i);
    }
    return out;
}

A few points:

  • you don't need an extra conditional to avoid an extra trailing delimiter
  • make sure you don't crash when the vector is empty
  • don't make a bunch of temporaries (e.g. don't do this: x = x + d + y)

1 Comment

This is the right approach. However, OP doesn't have a vector of strings and your function only supports a vector of strings.
2

There was absolultey no need for super fancy stuff. Simple stuff like this would have achieved the same results.

int vectorCounter=0;

//this is where you loop over the contents of your vector
for (auto it = StrVec.begin(); it != StrVec.end(); ++it){
                
    vectorCounter++;
    
    //this print contents of the vector
    cout << *it;

    //this will put a custom delimiter
    if (vectorCounter < StrVec.size())
    {
        //This is where you define your delimiter
        cout << ",";
    }
}//end-for-loop


OUTPUT:
1,2,3,4

Comments

2

Similar to another answer, but I just wanted to point out that there is an example on cppreference for std::accumulate

std::vector<int> v {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 
auto dash_fold = [](std::string a, int b)
{
  return std::move(a) + '-' + std::to_string(b);
};
 
std::string s = std::accumulate(std::next(v.begin()), v.end(),
                                std::to_string(v[0]),
                                dash_fold);
// s: 1-2-3-4-5-6-7-8-9-10

Comments

1

I know this is an old question, but I have a similar problem and none of the above answers suits all my needs, so I'll post here my solution.

My requirements are:

  • I need a generic solution able to work with any iterable container and with any data type, of course for custom data types you'll have to provide a suitable operator<<()
  • I need an easy way to apply transforms to the data (for example, by default int8_t and uint8_t are handled as chars by std::stringstream: maybe this is what you want or maybe not, so I want to be able to make this choice)
  • I want to be able to specify the delimiter as a string literal, but also accept chars and std::strings
  • I like to have the ability to add enclosing characters, but this is probably very personal taste

This assumes C++11. I choose to use std::stringstream because it implements a standard but still customizable way to convert something to a string. Any comments are very welcome.

#include <iterator>
#include <sstream>
#include <string>
#include <iostream> // used only in main
#include <vector> // used only in main

template< typename T >
typename std::iterator_traits< T >::value_type
identity(typename std::iterator_traits< T >::value_type v) {
  return v;
}

template< typename T > using IdentityType = decltype(identity< T >);

template< class InItr,
          typename StrType1 = const char *,
          typename StrType2 = const char *,
          typename StrType3 = const char *,
          typename Transform = IdentityType< InItr > >
std::string join(InItr first,
                 InItr last,
                 StrType1 &&sep = ",",
                 StrType2 &&open = "[",
                 StrType3 &&close = "]",
                 Transform tr = identity< InItr >) {

  std::stringstream ss;

  ss << std::forward< StrType2 >(open);

  if (first != last) {

    ss << tr(*first);

    ++first;
  }

  for (; first != last; ++first)
    ss << std::forward< StrType1 >(sep) << tr(*first);

  ss << std::forward< StrType3 >(close);

  return ss.str();
}


int main(int argc, char** argv) {

  const std::vector< int > vec{2, 4, 6, 8, 10};

  std::cout << join(vec.begin(), vec.end()) << std::endl;
  std::cout << join(vec.begin(), vec.end(), "|", "(", ")",
                    [](int v){ return v + v; }) << std::endl;

  const std::vector< char > vec2{2, 4, 6, 8, 10};
  std::cout << join(vec2.begin(), vec2.end()) << std::endl;
  std::cout << join(vec2.begin(), vec2.end(), "|", "(", ")",
          [](char v){ return static_cast<int>(v); }) << std::endl;
}

outputs something like:

[2,4,6,8,10]
(4|8|12|16|20)
[<unprintable-char>,<unprintable-char>,<unprintable-char>,
]
(2|4|6|8|10)

Comments

1

If the usage of ABSL is ok, you could do it like this:

std::vector<std::string> strings{"h", "e", "ll", "o"};
auto joined(absl::StrJoin(strings, "-"));

See godbolt

Boost has a similar thing like @StefanQ suggested

Comments

1

A very simple yet not terribly slow answer that I'm surprised I haven't seen yet:

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

std::string join(std::vector<std::string> &strings, std::string &delimiter) {
    std::string joined = "";
    for (std::string i : strings) joined += delimiter + i;
    return joined.erase(0, delimiter.size());
}

int main() {
    std::vector<std::string> strings = {"first", "second", "third"};
    std::string delimiter = " + ";
    std::cout << join(strings, delimiter) << "\n";
    
    return 0;
}

Output:

first + second + third

Comments

1

You can intersperse you vector with a delimiter into a target string like this:

#include <sstream>
#include <string>

template <typename Container> std::string join(const Container &c,
        const std::string &d = ",")
{
    std::ostringstream r;
    auto i = c.begin();
    auto e = c.end();
    if (i == e)
        return r.str();
    r << *i++;
    for ( ; i != e; ++i)
        r << d << *i;
    return r.str();
}

The function has a similar or even smaller line count than most of the other solutions, already posted, while avoiding arguably unnecessary obfuscation.

That way the code arguably is easier to maintain and reason about.

Also, in contrast to many other answers, the code doesn't test redundantly for the last element in order to avoid placing a trailing delimiter. This is a bit more efficient and less complex.

1 Comment

This is the best imo because of simplicity.
0
int array[ 6 ] = { 1, 2, 3, 4, 5, 6 };
std::vector< int > a( array, array + 6 );
stringstream dataString; 
ostream_iterator<int> output_iterator(dataString, ";"); // here ";" is delimiter 
std::copy(a.begin(), a.end(), output_iterator);
cout<<dataString.str()<<endl;

output= 1;2;3;4;5;6;

3 Comments

Generally, answers are much more helpful if they include an explanation of what the code is intended to do, and why that solves the problem without introducing others.
This includes a trailing delimiter. Granted, the OP didn't specify but usually one places delimiters between fields and not at the end of the final field.
Actually, this answer seems closer to answering the question than most others since the question is specifically about "ostream_iterator". Most answers focus on the more general functionality without using that function. The question is about join, but rather specifically about using that function. Maybe the asker doesn't care what functions they use. Maybe they do or don't care whether there's a trailing delim. Often hard to know the intent of the asker.
0

Another potential option is std::experimental::ostream_joiner:

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> i{1, 2, 3, 4, 5};
    std::copy(i.begin(), i.end(), 
              std::experimental::make_ostream_joiner(std::cout, ", "));

    return 0;
}

Output:

1, 2, 3, 4, 5

NOTE: As implied by the experimental namespace, this is not yet standardized and is currently not available on some compilers. e.g. I was able to use it in GCC and Clang, but not MSVC.

Comments

-2

faster variant:

vector<string> x = {"1", "2", "3"};
string res;
res.reserve(16);

std::accumulate(std::begin(x), std::end(x), 0,
                [&res](int &, string &s)
                {
                    if (!res.empty())
                    {
                        res.append(",");
                    }
                    res.append(s);
                    return 0;
               });

it doesn't create interim strings, but just allocate memory once for the whole string result and appends each elem to the end of &res

3 Comments

You're using an accumulate() but totally ignore that result. You should be using std::for_each() instead.
actually return value of this function call is 0 (see lambda -> return 0;). but 'res' contains result string
Did you know accumulate expects its function to not have side-effects? This code would better be expressed as a for_each.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.