3

How to define a function template-ed on a container and a type?

For example, overload insertion operator to stream all the elements of a vector, list, or, forward iterator container:

using namespace std;

#include <iostream>
#include <vector>
#include <list>


//...
//...the second argument is a container template-ed on type T
//...
template <typename T,template <typename U> class C>
ostream&
operator<<
  (ostream& p_os,const C<T>& p_c)
{
  for(typename C<typename T>::const_iterator cit=p_c.begin();cit!=p_c.end();++cit)
  {
    p_os.operator<<(*cit);
  }
  return p_os;
}

int
main
  ()
{
  vector<int> v;
  cout << v << endl;
  list<int> l;
  cout << l << endl;
  return 0;
}

This does not compile on g++ 4.9. What is wrong? How is it done?

silvermangb
  • 355
  • 2
  • 11

3 Answers3

1

std::vector is a class template that has two template type parameters:

template <class T, class Alloc = allocator<T> >
class vector;

To make your function working with std::vector (and other two-parameter class templates) you can use the following definition:

template <typename T, typename A, template <typename, typename> class C>
//                    ~~~~~~~~~^                      ~~~~~~~^
ostream& operator<<(ostream& p_os, const C<T,A>& p_c) 
//                                          ^^
{
  for(typename C<T,A>::const_iterator cit=p_c.begin();cit!=p_c.end();++cit)
  {
     p_os.operator<<(*cit);
  }
  return p_os;
}

or alternatively:

template <typename T, template <typename...> class C>
ostream& operator<<(ostream& p_os, const C<T>& p_c);
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • `vector` has *at least* two template parameters - an implementation can add more, as long as they have defaults. So the first version isn't portable. – Alan Stokes Dec 06 '14 at 21:28
  • @AlanStokes I don't believe you, where does the standard say so ? – Piotr Skotnicki Dec 06 '14 at 21:31
  • My apologies, this piece of "common knowledge" turns out not to be true. It was proposed but rejected: http://stackoverflow.com/questions/1469743/standard-library-containers-with-additional-optional-template-parameters – Alan Stokes Dec 06 '14 at 21:37
  • There is a default for the allocator, so, this approach is unnecessary if the default allocator is acceptable. – silvermangb Dec 07 '14 at 03:26
  • @silvermangb it's necessary for matching vector against template template parameter – Piotr Skotnicki Dec 07 '14 at 06:54
1

Why not just pass the container type as template parameter, and find out the element type from it? In your example code you don't even need the element type:

template <typename C>
ostream&
operator<<
  (ostream& p_os,const C& p_c)
{
  typedef typename C::value_type element_type; // if needed
  for(typename C::const_iterator cit=p_c.begin();cit!=p_c.end();++cit)
  {
    p_os.operator<<(*cit);
  }
  return p_os;
}

(Although it might be unwise to use this for global functions like this without some enable_if trickery, since it will otherwise match any argument.)

EDIT: You could for example attempt to restrict this to classes with a nested value_type (which all containers have):

template <typename C, typename T = typename C::value_type>
ostream&
operator<<
  (ostream& p_os,const C& p_c)
Alan Stokes
  • 18,815
  • 3
  • 45
  • 64
  • This approach does what I wanted. I have extended this to support streaming all container types – silvermangb Dec 07 '14 at 03:26
  • "(Although it might be unwise to use this for global functions like this without some enable_if trickery, since it will otherwise match any argument.)" I tried to use a comma to separate the entries of the container but the compiler tried to use that function to stream a string. How to avoid that with enable_if? – silvermangb Dec 07 '14 at 05:42
  • See edit (which doesn't actually use `enable_if`). For a more complex, and powerful, solution see http://stackoverflow.com/questions/9242209/is-container-trait-fails-on-stdset-sfinae-issue. – Alan Stokes Dec 07 '14 at 10:30
0

Alan Stokes approach works. The code below can stream any container. I just had to add an insertion operator for maps

using namespace std;

#include <iostream>

#include <vector>
#include <list>
#include <forward_list>
#include <set>
#include <deque>
#include <array>
#include <map>
#include <unordered_map>

//...
//...needed for map types which are (key,value) pairs.
//...
template <typename K,typename V>
ostream&
operator<<
  (ostream& p_os,const pair<const K,V>& p_v)
{
  std::operator<<(p_os,'(');
  p_os << p_v.first;
  std::operator<<(p_os,',');
  p_os << p_v.second;
  std::operator<<(p_os,')');
  return p_os;
}

template <typename C, typename T = typename C::iterator>
ostream&
operator<<
  (ostream& p_os,const C& p_c)
{
  for(typename C::const_iterator cit=p_c.begin();cit!=p_c.end();++cit)
  {
    typename C::value_type v = *cit;
    p_os << v;
    std::operator<<(p_os,",");
  }
  return p_os;
}

int
main
  ()
{
  vector<int> v;
  for(int i=0;i<4;++i)
  {
    v.push_back(i);
  }
  cout << v << endl;
  list<int> l;
  for(int i=0;i<4;++i)
  {
    l.push_back(i);
  }
  cout << l << endl;
  forward_list<int> fl = {0,1,2,3};
  cout << fl << endl;
  set<int> s;
  for(int i=0;i<4;++i)
  {
    s.insert(i);
  }
  cout << s << endl;
  deque<int> d;
  for(int i=0;i<4;++i)
  {
    d.push_back(i);
  }
  cout << d << endl;
  array<int,4> a = {0,1,2,3};
  cout << a << endl;
  unordered_map<int,int> um;
  for(int i=0;i<4;++i)
  {
    um[i] = i;
  }
  cout << um << endl;
  map<int,int> m;
  for(int i=0;i<4;++i)
  {
    m[i] = i;
  }
  cout << m << endl;
  return 0;
}
silvermangb
  • 355
  • 2
  • 11