Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make function argument container independent

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.
like image 704
cppcoder Avatar asked Mar 27 '11 18:03

cppcoder


People also ask

What is the difference between a function and a container?

A container can contain anything, from a webservice, to a million LOC monolith, to a complete DBMS system, to the data tier that DBMS writes into. These containers can live forever, scale horizontally (or not), take 20 minutes to start, 20 days to run, and on and on. Contrast this to a Function, which typically has a set of known characteristics:

What if we package a function into a container?

Now if we take our function with these characteristics and package it into a container, then we can now think and reason about that container differently, giving rise to a powerful concept-a new atomic unit of compute.

Can a function run independently of itself?

Although a function is likely just part of a broader grouping of functions (eg. many functions create an API, a sequence of functions create a “flow”), by itself, it can run independently as long as it gets its required input. [thanks Tobias Kunze for name suggestion]


2 Answers

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.

like image 183
Ben Voigt Avatar answered Oct 27 '22 15:10

Ben Voigt


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.

like image 33
Alexey Kukanov Avatar answered Oct 27 '22 16:10

Alexey Kukanov