将向量的向量打印到输出流中

4
请考虑以下代码。我试图将一个向量的向量输出到ostream。
#include <iterator>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

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

int main() {
    using namespace std;
    vector<string> v1;
    cout << v1;
    vector<vector<string> > v2;
    cout << v2;
    return 0;
}

输出字符串向量的语句是可行的。但是输出字符串向量的向量的语句不行。我使用的是g++ 4.7.0。我尝试过使用或不使用-std=c++11标志。在C++11模式下,它会给出半页错误信息中的这一行。

error: cannot bind 'std::ostream_iterator<std::vector<std::basic_string<char> >, char, std::char_traits<char> >::ostream_type {aka std::basic_ostream<char>}' lvalue to 'std::basic_ostream<char>&&'

我不确定我理解它的含义。能有人给我解释一下吗?我更或多或少知道什么是rvalue引用,但我不明白为什么std::basic_ostream<char>不能绑定到std::basic_ostream<char>&&。也许我还不够了解。还有更好的方法吗?

提前感谢。


考虑一下,在 vector<vector<string>> 的情况下,"\n" 必须能够转换为 vector<string>。当然这是行不通的。 - J-16 SDiZ
@J-16SDiZ:你在说什么?不,它并没有。 - Benjamin Lindley
@J-16SDiZ:不。那是错误的。 - Nawaz
2个回答

8
你收到的错误信息有些误导人。当我尝试编译你的程序时,我不得不深入挖掘模板代码,并最终理解了它的含义:
error: no match for 'operator<<' in '*((std::ostream_iterator<std::vector<std::basic_string<char> >, char, std::char_traits<char> >*)this)->std::ostream_iterator<std::vector<std::basic_string<char> >, char, std::char_traits<char> >::_M_stream << __value'

基本上,当您调用复制算法时,它使用了输出迭代器,该迭代器使用std命名空间内的<<运算符。一旦在此处,查找规定它尝试在std命名空间中找到模板vector<>的重载版本(因为那里是IT所在的位置)。
因此,您需要在std命名空间中声明vector模板的流运算符。将您的代码用namespace std {}包围起来,看看会发生什么...
应该注意的是,您正在修改std::vector<>并向其中添加以前不存在的行为。这样做是非标准的、未定义的,并且很容易挡住您的路。您可能要考虑其他选项。
我对这个是koenig查找的事情是错误的。不是的,问题是名称隐藏,类似于在这里声明一个基类中某个东西的重载(而不是覆盖)。
标准命名空间声明了几个' << '运算符。这些基本上是命名为operator <<的函数。实质上,您拥有的是:
void fun(int);

namespace Test {

  void fun() { fun(3); }

}

int main() {
    Test::fun();
}

请注意,您可以从全局命名空间或任何没有名为fun的函数的命名空间中使用fun(int)。 但是您无法从Test命名空间中使用它。
这就是为什么您在全局命名空间中声明的运算符<<可以正常工作,但在std命名空间中却无法正常工作的原因。 std命名空间已经有了与您尝试提供的重载相同的名称,因此该重载对std中的所有内容都是隐藏的。 如果您可以在那里放置一个using声明,则情况将不同。

把东西放在std命名空间中是不被标准允许的,对吧?不过,我非常怀疑它实际上会在任何真正的实现中引起问题,而且它似乎对我有效。 - Benjamin Lindley
好的,成功了!谢谢!不过我还是不太明白。如果它能在全局命名空间中找到 T 为 string 的 vector<string> 的重载,为什么它不能在第二级的两级查找中找到重载呢?输出迭代器代码在 std 命名空间中编写,是为了寻找 std::operator << 吗?我也同意 Benjamin 的担忧 - 这是鼓励的吗? - Ashley
这是未定义的行为,但你必须做你必须做的事情。如果它已经被定义了,它就已经可以工作了。 - Edward Strange
@Ashley - 这与调用的作用域有关。名称查找有点复杂,尤其是涉及模板时。请阅读关于koenig查找和其他名称查找规则的内容。 - Edward Strange
@CrazyEddie,谢谢。其他人可以参考Wikipedia上的解释。你会发现在这种情况下不会搜索全局命名空间。 - Ashley

4

您需要使用这个实用程序库:


如果您想自己完成此操作(以便自学),则需要定义两个重载函数:

  • For std::vector<T>:

    template<typename T>
    std::ostream &operator <<(std::ostream &os, const std::vector<T> &v) {
       using namespace std;
       copy(v.begin(), v.end(), ostream_iterator<T>(os, "\n"));
       return os;
    }
    
  • For std::vector<std::vector<T>>:

    template<typename T>
    std::ostream &operator <<(std::ostream &os, const std::vector<std::vector<T>> &v) {
       using namespace std;
    
       //NOTE: for some reason std::copy doesn't work here, so I use manual loop
       //copy(v.begin(), v.end(), ostream_iterator<std::vector<T>>(os, "\n"));
    
       for(size_t i = 0 ; i < v.size(); ++i)
            os << v[i] << "\n";
       return os;
    }
    

如果您有这些超载,那么它们将一起递归处理这些情况:

std::vector<int>  v;
std::vector<std::vector<int>>  vv;
std::vector<std::vector<std::vector<int>>>  vvv;
std::vector<std::vector<std::vector<std::vector<int>>>>  vvvv;

std::cout << v << std::endl; //ok
std::cout << vv << std::endl; //ok
std::cout << vvv << std::endl; //ok
std::cout << vvvv << std::endl; //ok

1
@BenjaminLindley:我不认为它不起作用的任何理由。也许是编译器的错误?无论如何,手动循环可以正常工作。 - Nawaz
另一个版本无法工作,因为ostream_iteratorstd命名空间中,vector也是如此。因此,ADL无法找到您定义的operator <<模板,并且编译失败。 - Dave S
@DaveS:我还不确定。那么手动循环为什么能够工作呢?它是如何找到我定义的 operator<< 模板的呢? - Nawaz
@Nawaz:我猜测这是因为你的operator<<在全局命名空间中,所以它也被搜索到了。实际上,如果你将OP的原始代码更改为使用手动循环(而不是复制/迭代器),则可以在不进行任何进一步修改的情况下正常工作(http://ideone.com/rVBY2)。嗯...这可能是模板的后期绑定中的一个错误。 - Dave S
@Nawaz - 如果你在第一个函数中避免使用std::copy,那么你就不需要第二个函数了。 - Robᵩ
显示剩余2条评论

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