【问题标题】:Overloading << operator: struct with vector of structs重载 << 运算符:带有结构向量的结构
【发布时间】:2021-02-20 20:09:04
【问题描述】:
  • 重载
  • 重载
  • 这种组合也很有效。

但是,如果我在带有结构向量的 结构上使用 ,编译将失败。 我做了一个小例子来展示这个问题:

#include <iostream>
#include <ostream>
#include <vector>

template <typename T>
std::ostream& operator<<(std::ostream& out, const std::vector<T>& v) {
    out << "[";
    for (auto it = v.begin(); it != v.end(); ++it) {
        out << *it;
        if (std::next(it) != v.end()) {
            out << ", ";
        }
    }
    out << "]";
    return out;
}

namespace xyz {

struct Item {
    int a;
    int b;
};

struct Aggregation {
    std::vector<Item> items; 
};

std::ostream& operator<<(std::ostream& out, const Item& item) {
    out << "Item(" << "a = " << item.a << ", " << "b = " << item.b << ")";
    return out;
}

std::ostream& operator<<(std::ostream& out, const Aggregation& agg) {
    out << "Aggregation(" << "items = " << agg.items << ")";
    return out;
}

}  // namespace xyz

int main() {
    xyz::Aggregation agg;
    agg.items.emplace_back(xyz::Item{1, 2});
    agg.items.emplace_back(xyz::Item{3, 4});

    std::cout << agg.items << std::endl;  // works: [Item(a = 1, b = 2), Item(a = 3, b = 4)]
    std::cout << agg << std::endl;        // fails, expected: Aggregation(items = [Item(a = 1, b = 2), Item(a = 3, b = 4))
}

编译器资源管理器链接:https://godbolt.org/z/a8dccf

<source>: In function 'std::ostream& xyz::operator<<(std::ostream&, const xyz::Aggregation&)':
<source>:35:41: error: no match for 'operator<<' (operand types are 'std::basic_ostream<char>' and 'const std::vector<xyz::Item>')
   35 |     out << "Aggregation(" << "items = " << agg.items << ")";
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^~ ~~~~~~~~~
      |                           |                    |
      |                           |                    const std::vector<xyz::Item>
      |                           std::basic_ostream<char>
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/iostream:39,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/ostream:108:7: note: candidate: 'std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__ostream_type& (*)(std::basic_ostream<_CharT, _Traits>::__ostream_type&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]'
  108 |       operator<<(__ostream_type& (*__pf)(__ostream_type&))
      |       ^~~~~~~~

我做错了什么?

【问题讨论】:

    标签: c++ vector struct overloading ostream


    【解决方案1】:

    main函数中,当你写下这一行时:

    std::cout << agg.items << std::endl;
    

    编译器将在全局命名空间中查找operator&lt;&lt; 的所有重载。正确的重载是通过重载解析选择的,因此调用有效。

    当你在这里写类似的代码时

    std::ostream& operator<<(std::ostream& out, const Aggregation& agg) {
        out << "Aggregation(" << "items = " << agg.items << ")";
        return out;
    }
    

    由于此代码在命名空间xyz 中,编译器将首先在命名空间xyz 中查找operator&lt;&lt; 的重载。一旦它发现任何重载,它将停止寻找额外的重载。但是,由于您想要的实际 operator&lt;&lt; 不在命名空间 xyz 中,因此重载解析失败,并且您会收到错误消息。

    解决此问题的方法是简单地将带有vector&lt;T&gt;operator&lt;&lt; 移动到命名空间xyz

    这是demo


    如果你真的想要一个operator&lt;&lt; 可以从全局范围和命名空间xyz 访问任何类型的vector,那么你可以在全局范围中定义它,就像你在你的题。然后只需将运算符带入xyz,或者最好将其带入命名空间xyz 中您需要它们的特定函数中,如下所示:

    namespace xyz 
    {
      // using ::operator<<;  // if you want all of `xyz` to see the global overload
     
      std::ostream& operator<<(std::ostream& out, const Aggregation& agg) 
      {
        using ::operator<<;  // if you only want the global overload to be visible in this function
        out << "Aggregation(" << "items = " << agg.items << ")";
        return out;
      }
    
      // ...
    }
    

    这是一个demo,它展示了如何流式传输vector&lt;int&gt;vector&lt;xyz::Item&gt;


    感谢@NathanPierson 指出using 声明可以在需要它的函数中是本地的,而不是污染整个命名空间xyz

    【讨论】:

    • 但是如果有人想使用operator&lt;&lt; 来表示std::vector&lt;int&gt;,它不会触发ADL,也不会搜索xyz 命名空间。有没有办法避免代码重复,以便该函数通常适用于向量,但在命名空间xyz 类的情况下仍然可以找到?
    • 太好了,谢谢!我想在整个项目中将向量的重载保留在一个地方。有什么方法可以在其他命名空间中访问,而无需一次又一次地重写函数?
    • @luxderfux 澄清一下,您希望任何类型的vectors 可以流式传输,还是只需要特定类型的向量?
    • @cigien: vectors 任何类型
    • 我要注意的一点是,您不必将其引入整个 xyz 命名空间,您也可以在特定函数中使用 using ::operator&lt;&lt;;流媒体Aggregates.
    【解决方案2】:

    我在 fmt 库 (https://github.com/fmtlib/fmt/issues/2093) 上再次遇到了类似的问题。另一个可行的解决方案似乎是将 std 容器的 operator&lt;&lt; 重载直接添加到命名空间 std:

    namespace std {
    
    template <typename T>
    std::ostream& operator<<(std::ostream& out, const std::vector<T>& v) {
        out << "[";
        for (auto it = v.begin(); it != v.end(); ++it) {
            out << *it;
            if (std::next(it) != v.end()) {
                out << ", ";
            }
        }
        out << "]";
        return out;
    }
    
    }  // namespace std
    

    编译器资源管理器链接:https://godbolt.org/z/o7c9WP

    不过,我对在命名空间 std 中添加一些东西感到难过。对此有什么想法吗?

    【讨论】:

      猜你喜欢
      • 2020-06-25
      • 2018-05-20
      • 1970-01-01
      • 2015-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多