【问题标题】:Why can't I instantiate operator<<(ostream&, vector<T>&) with T=vector<int>?为什么我不能用 T=vector<int> 实例化 operator<<(ostream&, vector<T>&)?
【发布时间】:2011-07-18 07:48:41
【问题描述】:

在思考C++ iterator question时,我写了这个示例程序:

#include <vector>
#include <iostream>
#include <iterator>
#include <algorithm> 

template <class T>
std::ostream& operator<<(std::ostream&os, const std::vector<T>& v) 
{ 
    os<<"(";
    std::copy(v.begin(), v.end(), std::ostream_iterator<T>(os, ", "));
    return os<<")";
}

int main()
{
    std::vector<int> v(3);
    std::vector<std::vector<int> > vv(3, v);
    std::cout << v << "\n"; // this line works
    std::cout << vv << "\n"; // this line produces error
}

我用 gcc 编译这个程序并得到典型的 100 行错误。我相信相关的部分是:

it.cc:19:从这里实例化

/usr/include/c++/4.4/bits/stream_iterator.h:191: 错误:'((std::ostream_iterator >, char, std::char_traits > )this)->std::ostream_iterator >, char, std::char_traits >::_M_stream

为什么会失败?在我的模板operator&lt;&lt; 中,我尝试指定任何矢量,无论其类型如何,都是可打印的。那么为什么std::vector&lt;std::vector&lt;&gt;&gt; 不打印呢?

编辑:在模板函数中使用以下代码使其工作

#if 0
    std::copy(v.begin(), v.end(), std::ostream_iterator<T>(os, ", "));
#else
    for(typename std::vector<T>::const_iterator it = v.begin();
        it != v.end();
        it++) {
        os<<(*it)<<", ";
    }
#endif

【问题讨论】:

  • 在英特尔 C++ 中也不起作用。
  • 用手动循环替换std::copy是否有效?
  • 我不知道发生这种情况的确切原因(我认为这与 ADL 有关)。但我发现,如果我将函数放入 std 命名空间,它就可以解决问题。虽然我不确定这是否是个好主意。
  • 谢谢@larsmans。它在clang++ 中也失败了。我认为问题出在我的程序中,而不是编译器。
  • 谢谢,@Benjamin。在std:: 中放任何东西似乎是个坏主意。

标签: c++ templates iostream


【解决方案1】:

两个词:名字查找。

这是您尝试做的简化示例,不需要任何标准库标头:

template <typename T> void f(T) { }

namespace ns {
    class C { };

    void f(int) { }

    void test() { f(C()); } // doesn't work :'(
}

int main() {
    f(ns::C());             // works!  :-D
}

在这个例子中,在main()中,在正常名称查找过程中唯一找到的f是全局命名空间中的函数模板,并且匹配,所以main使用它(ns::f也找到了在依赖于参数的查找期间,但它不匹配,因此在重载解析期间仍选择全局 f

但是,在 test 中,发现了 ns::f(int) 重载并且名称查找停止。命名空间是向外搜索的,所以首先搜索ns,然后是全局命名空间,但是一旦找到名称就会停止名称查找,所以一旦找到ns::f(int),名称查找就会停止。依赖于参数的查找也会发生并找到ns::f(int),因为C 在命名空间ns 中,然后ADL 停止搜索。

在您的示例中也是如此:在 main() 中,找到了 operator&lt;&lt; 重载,但在 std::ostream_iterator(位于 std 命名空间中)内部,找到了其他 &lt;&lt; 重载,并且所以找不到你的过载。

您的operator&lt;&lt; 重载需要在std 命名空间中才能工作,但遗憾的是,您不能将名称添加到std 命名空间。

【讨论】:

  • 感谢您的明确解释。通过消除 STL 来减少模板问题是一种很好的解释方法。
  • @Rob:您可能想阅读有关 Argument Dependent Lookup 的内容。供您参考...我只是在 std 命名空间中添加了这些重载。这是被禁止的,守护进程可能会飞出我的鼻子......但它对我有用(当发布新编译器时我必须检查它)。
  • 谢谢,@Matthieu。当您输入该评论时,我正在阅读en.wikipedia.org/wiki/Argument-dependent_name_lookup
  • @Johannes:我当然不会:)
  • @Johannes:不,我一点也不介意。请继续 :-)。我在这里做出贡献是为了希望我能更好地理解 C++,以便我能学会简洁而正确地解释这门语言。您的 cmets 和答案绝对是无价之宝,非常感谢(尤其是对于这样的问题;在名称解析、ADL 和重载解析方面,我仍然很菜鸟)。
【解决方案2】:

在函数模板的实例化上下文中查找仅使用 ADL。没有不合格的查找。因此,您需要依赖 ADL。但是 vector&lt;int&gt; 的 ADL 查找不包括全局命名空间,因此找不到您的 operator&lt;&lt;。试试这个,应该可以正常工作:

struct A { 
  operator int() const { return 0; }
};

template <class T>
std::ostream& operator<<(std::ostream&os, const std::vector<T>& v) 
{ 
    os<<"(";
    std::copy(v.begin(), v.end(), std::ostream_iterator<T>(os, ", "));
    return os<<")";
}

int main()
{
    std::vector<A> v(3);
    std::vector< std::vector<A> > vv(3, v);
    std::cout << vv << "\n"; // should work fine
}

这是因为全局命名空间与 std::vector&lt;A&gt; 的 ADL 查找集相关联(因为 A 是模板参数),因此在实例化 std::ostream_iterator&lt;&gt; 的各个成员函数时找到全局声明的模板,这将将您的operator&lt;&lt; 用于TA,然后将使用operator&lt;&lt;(int)std::ostream

【讨论】:

  • 酷。为了稍微重申你的答案,使用我原来的operator&lt;&lt; 模板,我可以cout&lt;&lt;vector&lt;vector&lt;MyClass&gt; &gt;(),即使我不能cout&lt;&lt;vector&lt;vector&lt;POD&gt; &gt;(),因为你所说的 ADL 原因。 (假设我可以cout&lt;&lt;MyClass(),当然)。谢谢!
【解决方案3】:

我认为由于std::copy定义在不同的命名空间中,而从函数模板std::copyostream_iterator类模板生成的代码,无法找到您定义的operator&lt;&lt;存在于不同的命名空间中。

要解决此问题,您必须在 std 命名空间中定义 operator&lt;&lt;,如下所示:

namespace std //note the namespace
{
   template <class T>
   std::ostream& operator<<(std::ostream&os, const std::vector<T>& v) 
   { 
     os<<"(";
      std::copy(v.begin(), v.end(), std::ostream_iterator<T>(os, ", "));
      return os<<")"; 
   }
}

ideone 的工作代码:http://ideone.com/sFenn

但是,我不能说在 std 命名空间中实现它的想法有多好!


或者,您可以将operator&lt;&lt; 定义为(不使用std::copy):

template <class T>
std::ostream& operator<<(std::ostream&os, const std::vector<T>& v) 
{ 
   typedef typename std::vector<T>::const_iterator const_iterator;
   os<<"(";
   for (const_iterator it = v.begin() ; it != v.end() ; ++it )
       os << *it << ", ";
   return os<<")"; 
}

工作代码:http://ideone.com/FXWlP

【讨论】:

  • 无法将重载添加到 std 命名空间。
  • 谢谢!英雄所见略同。请参阅@FredOverflow 对我的问题的评论以及我随后的编辑。
  • @James:我正要怀疑。无论如何,我在答案中又添加了一行。顺便说一句,您认为哪些重载不能添加到 std 命名空间?
  • 未定义的行为。 17.4.3.1 “除非另有说明,否则 C++ 程序将声明或定义添加到命名空间 std 或命名空间 std 中的命名空间是未定义的。”
  • 因为标准库实现的作者需要能够知道,当他们对一个函数进行合格调用时,它调用的是他们期望调用的函数,而不是一些用户添加的重载。
猜你喜欢
  • 2022-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-02
  • 2023-01-20
  • 1970-01-01
相关资源
最近更新 更多