51

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?

Michael Kristofik
  • 34,290
  • 15
  • 75
  • 125
nkint
  • 11,513
  • 31
  • 103
  • 174
  • 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. – CashCow Feb 14 '12 at 13:54
  • 1
    The most elegant way is to use boost::algorithm::join() for this as described in http://stackoverflow.com/a/6334153/2056686 – StefanQ Nov 23 '16 at 12:55

12 Answers12

32

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.

jpalecek
  • 47,058
  • 7
  • 102
  • 144
  • 41
    +1, but also note that this will write a trailing `delimiter` in the output. – David Rodríguez - dribeas Feb 14 '12 at 13:49
  • 2
    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. – wilhelmtell Feb 14 '12 at 13:56
  • 1
    "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. – Steve Jessop Feb 14 '12 at 14:33
23

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;
                                });
max.kondr
  • 295
  • 3
  • 5
  • 2
    Looks neat, but wouldn't this create many strings in the process? Any way to improve this using string stream? – oferei Mar 31 '16 at 07:50
  • no, sttringsream is slower even more. in fact if you have only container of strings it is better to write usual for-loop like `code` string result; result.reserve(128); for (auto &it: x) { if (!result.emty()) { result.append(","); } result.append(it); } `code` – max.kondr Apr 01 '16 at 21:28
  • Sounds reasonable, but that's not what the code above does. It's not appending - it's generating new strings. – oferei Apr 03 '16 at 10:00
  • You can avoid the conditional if you start from `begin(x)+1`, with `*begin(x)` as initial element. – xtofl Sep 13 '16 at 11:57
  • 1
    @xtofl that will fail for an empty vector where `begin == end` – 463035818_is_not_an_ai May 09 '18 at 11:08
  • 1
    any reason you are passing non-const references? – 463035818_is_not_an_ai May 09 '18 at 11:09
  • Great solution to join operation. But producing initial delimiter. If you don't want to delimit empty strings use rather `return B.empty() ? A : (A.empty() ? B : A + " " + B);` And the signature of lambda could be `[](const std::string& A, const std::string& B)`. – VojtaK Jun 18 '21 at 10:00
  • great answer - especially if you make it a constexpr function; then all the performance issues everyone raises go away – UKMonkey Mar 10 '23 at 15:54
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;
}
Matt Balvin
  • 139
  • 1
  • 3
  • I love that you provided performance numbers -- very useful for making a decision one way or the other, thanks! – cyberbisson Sep 28 '17 at 14:35
  • Just to state the obvious: `concat_stream()` and `concat_accumulate()` only work for non empty vectors. – nriedlin Nov 17 '21 at 07:43
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)

Shadow2531
  • 11,980
  • 5
  • 35
  • 48
9
std::string Gesture::getLabeledPointsString(const std::string delimiter) {
  return boost::join(getLabeledPoints(), delimiter);
}

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

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    ehehehheheh is there a cpp without boost? +1 for boost, thanks! – nkint Feb 14 '12 at 19:08
  • @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 ;) – Matthieu M. Feb 14 '12 at 19:37
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)
Glen Knowles
  • 438
  • 6
  • 8
2

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)
davnat
  • 51
  • 1
  • 6
1

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
Sam B
  • 27,273
  • 15
  • 84
  • 121
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

schoetbi
  • 12,009
  • 10
  • 54
  • 72
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.

0x5453
  • 12,753
  • 1
  • 32
  • 61
-1
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;

Awais Rafique
  • 466
  • 6
  • 17
  • 1
    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. – Dan Cornilescu Apr 24 '16 at 17:01
  • 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. – Jeff Benshetler Mar 13 '19 at 16:15
-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

max.kondr
  • 295
  • 3
  • 5
  • You're using an `accumulate()` but totally ignore that result. You should be using `std::for_each()` instead. – Alexis Wilke Jul 12 '16 at 23:07
  • actually return value of this function call is 0 (see lambda -> return 0;). but 'res' contains result string – max.kondr Jul 15 '16 at 06:13
  • Did you know `accumulate` expects its function to not have side-effects? This code would better be expressed as a `for_each`. – xtofl Sep 13 '16 at 11:52