【问题标题】:Overload for ostream on vectors throws error when using std::copy使用 std::copy 时,向量上的 ostream 重载会引发错误
【发布时间】:2026-01-13 02:00:02
【问题描述】:

我想知道为什么以下编译失败:

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

template <template <typename...> class Container, class T>
std::ostream& operator<<(std::ostream& oss, const Container<T>& c) {
    oss << "[";
    std::copy(std::cbegin(c), std::prev(std::cend(c)), std::ostream_iterator<T>(oss, ","));
    return oss << (*std::crbegin(c)) << "]";
}

auto main() -> int {
    std::vector<std::vector<unsigned>> data(5);
    std::cout << data << std::endl;
    return 0;
}

http://coliru.stacked-crooked.com/a/431617423f92ba4e

当我执行以下一个时,它编译得很好:

  • 删除带有std::copy 的行
  • 将向量更改为一维向量(例如std::vector&lt;unsigned&gt; data(5))。

导致错误的std::copy 是什么?


使用 clion 进行调试,这是在 crbegin 行中打印的嵌套向量的类型:

【问题讨论】:

  • 另外,您似乎正试图在 std::vector&lt;unsigned&gt; 上调用 &lt;&lt;,并且不存在重载。
  • @PaulMcKenzie,如果我删除 std::copy 行,它编译得很好
  • template &lt;typename...&gt; class Container 就在这行,里面只能放一个容器,里面有两个std::vector&lt;std::vector&lt;unsigned&gt;&gt;
  • @smac89 因为容器无法从 ostream 中的 std::vector&lt;std::vector&lt;unsigned&gt;&gt; 复制 std::vector&lt;unsigned&gt;...
  • @Ruks,我认为这不是问题所在。重载绝对匹配嵌套向量。问题是当它尝试使用std::copy 打印内容时

标签: c++ stream operator-overloading c++14


【解决方案1】:

因为您的operator&lt;&lt;std 实体不可见。

注意std::ostream_iterator&lt;T&gt;输出值as if through operator&lt;&lt;,并根据[temp.dep.res]/1

在解析依赖名称时,来自以下来源的名称是 考虑:

  • 在模板定义处可见的声明。
  • 来自与函数参数类型关联的命名空间的声明都来自实例化上下文 ([temp.point]) 并从定义上下文中。

...您的operator&lt;&lt;std::ostream_iterator&lt;T&gt; 的定义点和命名空间std 中都不可见,因此std::ostream_iterator&lt;T&gt; 中使用的operator&lt;&lt; 无法正确解析。

【讨论】:

  • 好像是这样。在 Visual Studio 2017 中编译代码时,错误正好指向 std::ostream_iterator,并且无法为 std::vector&lt;unsigned&gt; 解析 operator &lt;&lt;
【解决方案2】:

这里有一个实验可以引导你找到答案:

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

namespace std {
template <template <typename...> class Container, class T>
std::ostream& operator<<(std::ostream& oss, const Container<T>& c) {
    oss << "[";
    std::copy(std::cbegin(c), std::prev(std::cend(c)), std::ostream_iterator<T>(oss, ","));
    return oss << (*std::crbegin(c)) << "]";
}
}

int main() {
    std::vector<unsigned> t{ 1, 2, 3 };
    std::vector<std::vector<unsigned>> data(5, t);
    std::cout << data << std::endl;
    return 0;
}

旁注:不定义t,它会编译(即,你的operator&lt;&lt;会在需要时找到),但如果你尝试运行它,它会崩溃——尝试使用std::prev(std::cend(c)) on空容器不会有好的结局。

当然,这确实违反了标准的要求(不允许像这样在命名空间 std 内定义 operator&lt;&lt;),但至少对于典型的编译器,现在可以找到名称,以便编译代码。

【讨论】:

    【解决方案3】:

    您的operator&lt;&lt;std::ostream_iterator 不可见,因此它无法在输入容器的元素上调用您的operator&lt;&lt;。只需使用手动循环,它就会按预期工作。

    另外,std::prev(std::cend(c))*(c.crbegin()) 在容器为空时未定义,因此请注意这一点。

    此外,std::vector(与大多数其他标准容器一样)具有多个模板参数,因此请在操作员的模板参数中使用 typename... Ts 而不是 class T

    试试这个:

    #include <vector>
    #include <iterator>
    #include <iostream>
    
    template < template<typename...> class Container, typename... Ts>
    std::ostream& operator<<(std::ostream& oss, const Container<Ts...>& c) {
        oss << "[";
        if (!c.empty()) { // use std::empty() in C++17 and later
            auto last = std::prev(std::cend(c));
            /*
            using value_type = typename Container<Ts...>::value_type;
            std::copy(std::cbegin(c), last, std::ostream_iterator<value_type>(oss, ","));
            */
            for(auto iter = std::cbegin(c); iter != last; ++iter) 
                oss << *iter << ",";
            oss << *last;
        }
        return oss << "]";
    }
    
    int main() {
        std::vector<std::vector<unsigned>> data(5);
        std::cout << data << std::endl;
        return 0;
    }
    

    输出

    [[],[],[],[],[]]
    

    Live Demo

    【讨论】:

      最近更新 更多