模板类作为模板参数

3

如何定义一个基于容器和类型的函数模板?

例如,重载插入运算符以流式输出向量、列表或前向迭代器容器中的所有元素:

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;
}

这段代码在 g++ 4.9 上无法编译。出了什么问题?应该怎样修改?

3个回答

1

std::vector 是一个类模板,有两个模板类型参数:

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

为了使您的函数与std::vector(以及其他两个参数的类模板)配合使用,您可以使用以下定义:
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;
}

或者选择另一种方式:
template <typename T, template <typename...> class C>
ostream& operator<<(ostream& p_os, const C<T>& p_c);

“vector” 至少有两个模板参数 - 只要它们有默认值,实现可以添加更多。因此,第一个版本不具备可移植性。 - Alan Stokes
@AlanStokes 我不相信你,标准在哪里说了这个? - Piotr Skotnicki
抱歉,这个“常识”事实上并不正确。它曾经被提出但被拒绝了:https://dev59.com/p3M_5IYBdhLWcg3wPAZF - Alan Stokes
如果默认分配器可接受,则存在默认分配器,因此如果默认分配器可接受,则此方法是不必要的。 - silvermangb
@silvermangb 需要使用模板参数将向量与模板进行匹配。 - Piotr Skotnicki

1
为什么不将容器类型作为模板参数传递,并从中找到元素类型?在你的示例代码中,甚至不需要元素类型:
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;
}

尽管这样做可能不明智,因为它会匹配任何参数,但您可以尝试将其限制为具有嵌套的value_type(所有容器都有)的类。请注意,这种方法不适用于全局函数,除非使用一些enable_if技巧。
template <typename C, typename T = typename C::value_type>
ostream&
operator<<
  (ostream& p_os,const C& p_c)

这种方法实现了我想要的功能。我已经扩展了它以支持流式传输所有容器类型。 - silvermangb
(虽然这样做可能不明智,因为它会匹配任何参数,所以在像全局函数这样的情况下最好使用一些 enable_if 技巧。)我尝试使用逗号来分隔容器的条目,但编译器试图使用该函数来流式传输字符串。如何通过 enable_if 避免这种情况? - silvermangb
请参见编辑(实际上不使用enable_if)。对于更复杂和强大的解决方案,请参见http://stackoverflow.com/questions/9242209/is-container-trait-fails-on-stdset-sfinae-issue。 - Alan Stokes

0

Alan Stokes的方法很有效。下面的代码可以流式传输任何容器。我只需要为映射添加插入运算符即可。

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;
}

仍然不完全正确。会导致 std::string 的 operator<< 函数产生歧义。 - silvermangb
好的,“string”是一个容器,所以它应该匹配它。 - Alan Stokes
尽管如此,我仍希望对字符串进行一些变通处理。长期以来,我一直想要序列化任何容器 operator<< 的方法,而不会防止字符串或其他对象被序列化。 - silvermangb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接