简体   繁体   中英

Overloading << operator: struct with vector of structs

  • Overloading << for vectors works.
  • Overloading << for custom structs works.
  • The combination works as well.

But if I use the << operator on a struct with a vector of structs , compilation fails. I made up a little example to showcase the problem:

#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))
}

Link to compiler explorer: 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&))
      |       ^~~~~~~~

What am I doing wrong?

In the main function, when you write this line:

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

the compiler will look in the global namespace for all overloads of operator<< . The correct overload is chosen via overload resolution, and so the call works.

When you write the similar code here

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

since this code is in namespace xyz , the compiler will first look up the overloads of operator<< in namespace xyz . Once it finds any overloads at all, it will stop looking for additional overloads. However, since the actual operator<< that you want is not in namespace xyz , overload resolution fails, and you get an error.

The fix for this is to simply move the operator<< taking a vector<T> into namespace xyz .

Here's a demo .


If you actually want an operator<< that takes a vector of any type to be accessible from the global scope as well as namespace xyz , then you can define it in the global scope as you have done in your question. Then just bring the operator into xyz , or preferably, into the specific functions in namespace xyz where you need them, like this:

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

  // ...
}

Here's a demo that shows how to stream a vector<int> as well as a vector<xyz::Item> .


Thanks to @NathanPierson for pointing out that the using declaration can be local to the functions where it's needed, instead of polluting the entirety of namespace xyz .

I ran over a similar issue again with the fmt library ( https://github.com/fmtlib/fmt/issues/2093 ). Another working solution seems to be adding operator<< overloading for std containers directly to namespace 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

Link to compiler explorer: https://godbolt.org/z/o7c9WP

I feel bad about adding something to namespace std though. Any thoughts on this?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM