8

I'm writing a utility function which will take a vector of elements (could be string, int, double, char) and concatenate into a single string and return it. It looks like this:

template<typename T>
std::string convert2Str(std::vector<T> const& vec) 
{
   std::ostringstream sStream; 
   for (size_t k=0; k<vec.size(); ++k) {
      sStream << vec[k] << " "; 
   }
   return sStream.str(); 
}

I would like to make this function more generic:

  • First use iterators instead of using indices for the vector<T>. I tried this std::vector<T>::const_iterator it = vec.begin() before the loop and the compiler gave me an error: : error: expected ; before it When I change the above defintions to std::vector<std::string>::const_iterator it = vec.begin() the error goes away. So, it looks like I'm not following correct syntax, please let me know what it is
  • Second is to make the function more generic by making the first argument container independent. Given any container (vector, list, queue, deque, etc.) I want to do the same thing as above. I tried searching for this in stackoverflow and did not find satisfactory answer.
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
cppcoder
  • 1,194
  • 4
  • 16
  • 30

5 Answers5

7

Step 1, as you said, use iterators:

template<typename T>
std::string convert2Str(std::vector<T> const& vec) 
{
   typedef std::vector<T> container;
   std::ostringstream sStream; 
   for (typename container::const_iterator it = vec.begin(); it != vec.end(); ++it) {
      sStream << *it << " "; 
   }
   return sStream.str(); 
}

Step 2, make the template argument the container type instead of the element type (you can get the element type back with value_type:

template<typename container>
std::string convert2Str(container const& vec)
{
   typedef container::value_type T; // if needed
   std::ostringstream sStream; 
   for (typename container::const_iterator it = vec.begin(); it != vec.end(); ++it) {
      sStream << *it << " "; 
   }
   return sStream.str(); 
}

In C++0x, this gets even simpler (and typename is not needed):

template<typename container>
std::string convert2Str(container const& vec)
{
   using std::begin;
   using std::end;
   std::ostringstream sStream;
   for (auto it = begin(vec); it != end(vec); ++it) {
      typedef decltype(*it) T; // if needed
      sStream << *it << " "; 
   }
   return sStream.str(); 
}

Among other advantages, std::begin and std::end work for raw arrays.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • what's the difference between his point 1 and yours. using std::vector::const_iterator doesnt work, but type deffing it does? I am bewildered. HIs solution is just missing a typename in front of the iterator declaration. (As the compiler states) But why? It feels like, a missing struct in c in front of a non typedeffed type. – Ronny Brendel Mar 27 '11 at 18:37
  • @Ronny: It's the `typename` keyword that's needed there. The typedef is just used to ease the transition to generic container (both `T` and `container` are still defined, but I switch which one is the template argument). – Ben Voigt Mar 27 '11 at 18:39
  • I guess the line "typedef container::value_type T; // if needed" is what tells the compiler that container is a vector, list, etc. and ensures that convert2Str cannot be called with a simple data type such as int or double. So, why is this statement marked "if needed"..Thanks for prompt answers. – cppcoder Mar 27 '11 at 19:18
  • @srikrish: That line only gets you back the `T` type which existed in your original code. This function didn't use `T` anywhere, but I wanted to show you how to access it just in case. Even without this line, if you tried to pass in something that wasn't a container, the compiler would complain about the calls to `begin` and `end`. – Ben Voigt Mar 27 '11 at 19:27
  • So if T is a user defined type (my own class), can I do something like this sStream << T.getValue() << " "; ?? – cppcoder Mar 27 '11 at 19:32
  • @srikrish: No, you can't use `.` after a type. But you might want to declare a local variable of type `T`, maybe something like `T sum = *it + 5;`. – Ben Voigt Mar 27 '11 at 19:36
6

Following STL practice, I would recommend using two iterators for input parameters, instead of a container (for obvious reason of being able to work with only a part of a container, and generally with any sequence defined by iterators):

template<typename InputIterator>
std::string convert2Str(InputIterator first, InputIterator last)
{
    std::ostringstream sStream;
    for (InputIterator it = first; it != last; ++it) {
       sStream << *it << " ";
    }
    return sStream.str();
}

In case you need the type of contained objects, use

typedef typename std::iterator_traits<InputIterator>::value_type T;

ADDED: You then can use the function as follows:

std::vector<int> int_vec;
std::list<float> f_list;
std::deque<std::string> str_deq;

     // put something into the containers here

std::cout<< convert2Str(int_vec.begin(), int_vec.end()) <<std::endl;
std::cout<< convert2Str(f_list.begin(), f_list.end()) <<std::endl;
std::cout<< convert2Str(str_deq.begin(), str_deq.end()) <<std::endl;

Note that you cannot iterate over std::queue; but if you really need it, the standard guarantees enough support for a do-it-yourself solution. See more info here: std::queue iteration.

Community
  • 1
  • 1
Alexey Kukanov
  • 12,479
  • 2
  • 36
  • 55
3

It's easiest if you templatize on the container type only; the value type is stored in all standard, Boost and Qt containers as the typedef member value_type. std::copy and ostream_iterator allow you to skip the lengthy iterator declarations.

template <typename Container>
std::string convert2Str(Container const &cont)
{
    std::ostringstream s;
    std::copy(cont.begin(), cont.end(),
              std::ostream_iterator<typename Container::value_type>(s, " "));
    return s.str();
}

The typename is necessary to avoid ambiguity. Recent versions of GCC will warn you when you omit this keyword.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • Of course, this doesn't actually teach srikrish how to write a generic function of his own, which is also a useful skill. And `ostream_iterator` will be better with C++0x. – Ben Voigt Mar 27 '11 at 18:41
  • @Ben: `decltype` is confusing; it seems like you're running/evaluating an expression, while really you aren't. Any STL-compliant container will have `value_type` (the standard containers do, the Boost containers do and even those in Qt). – Fred Foo Mar 27 '11 at 18:46
  • @larsman: Sorry, it really should be `ostream_iterator`. Raw arrays don't have `value_type`, but they do work with `std::begin` and `std::end`. And it definitely should be `using std::copy; copy(...);` to let Koenig lookup do its thing. Remember that function templates can't be partially specialized, and defining new overloads in `namespace std` is forbidden, the only way to provide an optimized version of `copy` for a container is using ADL. – Ben Voigt Mar 27 '11 at 18:47
  • * Is this code portable to platforms such as solaris? I recently used std::count in my code and sun compiler in solaris complained about this. Can you explain what does this statement "std::ostream_iterator(s, " ")" do? thanks – cppcoder Mar 27 '11 at 20:42
  • @srikrish: it works on any standard-conforming C++ compiler, but I don't know if the Sun compiler is conforming. See http://www.cplusplus.com/reference/std/iterator/ostream_iterator/. – Fred Foo Mar 27 '11 at 21:03
2

Use this. You need the typename part in order to tell the compiler that it should consider T::const_iterator a type while parsing, it can't really know that this is true until you actually call the function passing some T that has a const_iterator member type.

template<typename T>
std::string convert2Str(T const& cont) 
{
    std::ostringstream sStream; 
    for (typename T::const_iterator it = cont.begin(); it != cont.end(); ++it) {
        sStream << *it << " "; 
    }
    return sStream.str(); 
}
Erik
  • 88,732
  • 13
  • 198
  • 189
0

I think this should work:

template<typename T>
std::string convert2Str(T const& container) 
{
   std::ostringstream sStream; 
   for (typename T::const_iterator i= container.begin(); i != container.end(); ++i) {
      sStream << *i << " "; 
   }
   return sStream.str(); 
}

Demo : http://ideone.com/9pUVV

Nawaz
  • 353,942
  • 115
  • 666
  • 851