Reputation: 355
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?
Upvotes: 3
Views: 125
Reputation: 18964
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)
Upvotes: 1
Reputation: 355
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;
}
Upvotes: 0
Reputation: 48457
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);
Upvotes: 1