开发者

how to templatize a function on an stl container that has an iterator

开发者 https://www.devze.com 2023-02-03 09:45 出处:网络
I would like to write a function that will take an stl container (like set, vector or list) and then iterate over the contents and then append them to a string and give back the string.

I would like to write a function that will take an stl container (like set, vector or list) and then iterate over the contents and then append them to a string and give back the string.

Something like this.

开发者_如何学C// I dont know how to do this. Just using stl::container for meanings sake Not sure if such a thing exists?
template<typename T, typename Container = stl::container<T> >  
void JoinToString(const Container<T> cont, const char * delim, string &str)
{
   stringstream s;
   Container<T>::const_iterator it = cont.begin, last = cont.end();
   while(it != last)
   {
       s<<(*it);
       ++it;
       if(it == last)
           break;
       s<<delim;
   }
   str = s.str();
} 

I want something to this effect. Not sure how to write such a code.


The STL style is to pass in begin and end iterators to any algorithm, not the container itself: this keeps things general, and allows the use of native vectors with pointers. General C++ style considerations would also suggest returning a std::string instead of using a reference parameter.


You need to decide what you want. You can either pass a type or a template. Not both. In the code you posted you declare Container to a be type, but use it as a template.

template<typename T, typename Container = vector<T> > 
void test() { Container x; };

template<typename T, template <typename> class Container = vector > 
void test() { Container<T> x; } 


If you really need access to the container, then this will do what you want:

template<typename Container>  
void JoinToString(const Container& cont, const char * delim, string &str)
{
  typedef typename Container::value_type T;
  ...
}

However, it's more idiomatic to use an iterator range like this:

template<typename FwdIt>  
void JoinToString(FwdIt it, FwdIt end, const char * delim, string &str)
{
  typedef typename std::iterator_traits<Container::iterator>::value_type T;
  while(it != end)
  {
     ...
  }
}


Another solution which does exactly what you want is boost::algorithm::join :

This algorithm joins all strings in a 'list' into one long string. Segments are concatenated by given separator.

Example of use :

#include <boost/algorithm/string/join.hpp>
#include <boost/assign/list_of.hpp>
#include <iostream>
#include <string>
#include <vector>

int main()
{
    std::vector<std::string> v = boost::assign::list_of("A")("B")("C");
    std::cout << boost::algorithm::join(v, "/") << std::endl;
}

Outputs : A/B/C


Have a look at remove char from stringstream and append some data

Such a function does not exist.


This is a working example,

template<typename T>  
std::string JoinToString(const T& cont, const char* delim, std::string &str)
{
   std::stringstream s;
   T::const_iterator it= cont.begin();
   T::const_iterator last= cont.end();
   while(it != last)
   {
      s << (*it);
      ++it;
      s << delim;
      if (it == last)
         break;
   }
   return s.str() + str;
} 

int main()
{
   std::string s("String! ");
   std::vector<std::string> v(1, "String!, String!");
   std::cout << JoinToString(v, ", ", s) << "\n";

   std::list<std::string> l(1, "String!, String!");
   std::cout << JoinToString(l, ", ", s);
}

There are a few things worth noting though. You could use template<template<class> class T, although it might cause problems, depending on the amount of template arguments the container has.

I would like to note (for future reference), if you want to plug in a type into a class template, e.g. a std::string as a template argument into a std::vector the safest solution is,

template<class T>
struct something
{
   typedef typename boost::mpl::apply<T, std::string>::type type;
};
something<std::vector<boost::mpl::placeholders::_1>>::type;  

The reason this is safer, than using a template<template<class> class T, is that it will allow more customisation from the user side and will work on class templates with any amount of arguments/default arguments.


Your solution is almost correct. Just do this:

template<typename Container >  
string JoinToString(const Container & cont, const string &delim)
{
    stringstream s;
    for (Container::const_iterator it = cont.begin(); it != cont.end(); it++ )
    {
            s<<(*it);
            if ( (it+1) != cont.end() )
                   s<<delim;
    }
    return s.str();
} 

Better function would be this:

template<typename FwdIt>  
string JoinToString(FwdIt from, FwdIt to, const string &delim)
{
    stringstream s;
    for (; from != to; from++ )
    {
        s<<(*from);
        if ( (from+1) != to )
            s<<delim;
    }
    return s.str();
}

Using this decide from and to using which to join the elements!


Create custom output iterator:

struct append_to_string_with_delim
  : std::iterator<std::output_iterator_tag, void, void, void, void>
{
  append_to_string_with_delim(std::string &ss, char const *dd) : s(ss), d(dd)
  {
  }
  template<typename T>
  append_to_string_with_delim &operator=(T const &t)
  {
    std::ostringstream o;
    o << t;
    s += o.str();
    s += d;
    return(*this);
  }
  append_to_string_with_delim &operator*()
  {
    return(*this);
  }
  append_to_string_with_delim &operator++()
  {
    return(*this);
  }
  append_to_string_with_delim const &operator++(int)
  {
    return(*this);
  }
  std::string &s;
  char const *const d;
};

and use std::copy:

std::vector<int> v;
std::string s("The v vector elements are: ");
...
copy(v.begin(), v.end(), append_to_string_with_delim(s, " "));
0

精彩评论

暂无评论...
验证码 换一张
取 消