Home > front end >  Writing contents of an STL Map to output stream using ostream_iterator
Writing contents of an STL Map to output stream using ostream_iterator

Time:02-28

I have a map<string, int> object and I want to use ostream_iterator to write the contents of it to the screen or a file. I have overloaded output operator (operator<<) so that it can be used to write objects of type pair<const string, int> to an output stream, but when I try to compile the code I get the following error message:

error: no match for ‘operator<<’ (operand types are ‘std::ostream_iterator<std::pair<const std::__cxx11::basic_string, int> >::ostream_type’ {aka ‘std::basic_ostream’} and ‘const std::pair<const std::__cxx11::basic_string, int>’)
207 | *_M_stream << __value;

I ended up using for_each function to write the contents, but I was curious to find out if there's a way to use the stream iterator to do the job. Here's an excerpt of the code:

typedef map<string, int>::value_type map_value_type;

ostream &operator<<(ostream &out, const map_value_type &value) {
  out << value.first << " " << value.second;
  return out;
}

int main() {
  map<string, int> m;

  // code to fill the map

  // The following works with no problem
  for_each(m.begin(), m.end(), [](const map_value_type &val) { cout << val << endl; });

  // This line will not compile
  copy(m.begin(), m.end(), ostream_iterator<map_value_type>(cout, "\n"));
}

Curiously, when I force the compiler to give the full type names of parameters used in the operator<< function above, they exactly match the types mentioned in the error message, but for some reason compiler does not recognize to use it. I'm using g (Ubuntu 9.3.0-17ubuntu1~20.04) with -std=gnu 17 flag, but Visual Studio compiler (cl.exe version 19.29.30140) will give the same error.

I have also tried the following for operator<< without any success:

ostream &operator<<(ostream &out, const pair<string, int> &val);
ostream &operator<<(ostream &out, const pair<const string, int> &val);
ostream &operator<<(ostream &out, pair<const string, int> &val);
ostream &operator<<(ostream &out, pair<string, int> val);
ostream &operator<<(ostream &out, pair<const string, int> val);

template <typename key, typename value>
ostream &operator<<(ostream &out, const pair<key, value> &val) { ... }

All of the above mentioned functions work with the for_each approach, but none of them work with ostream_iterator.

What am I missing?!

CodePudding user response:

std::ostream_iterator uses << internally.

When it is instantiated for a type, << will find operator<< overloads only via argument-dependent lookup from the point of instantiation, not via normal unqualified name lookup.

For type pair<const string, int> (the element type of map<string, int>) the namespace considered for argument-dependent lookup is only ::std, because both pair and string are defined in it. Your overload in the global namespace will not be considered.

If you had a type like pair<MyClass, int>, where MyClass is a class you defined yourself at global scope, the overload would work, because then the global namespace scope would be part of argument dependent lookup as associated namespace of the template argument MyClass.

The version using a lambda works because it does normal unqualified lookup from the point of definition of the lambda as well, which finds the overload in the global namespace.

Unfortunately, as far as I am aware, there is no standard-conform way to overload operator<< for a standard library container specialization which doesn't depend on a custom type, so that it will be found via ADL e.g. by std::ostream_iterator.

Standard-conform is the issue, since the standard forbids adding overloads of operator<< to namespace std, which otherwise would technically solve the issue.

  • Related