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_tanduint8_tare handled aschars bystd::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 andstd::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)