Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload of operator<< not found when called from std::ostream_iterator?

Tags:

c++

c++11

This program

// main.cpp

#include <iostream>
#include <utility>
#include <algorithm>
#include <iterator>
#include <map>

template<typename t1, typename t2>
std::ostream& operator<<(std::ostream& os, const std::pair<t1, t2>& pair)
{
  return os << "< " << pair.first << " , " << pair.second << " >";
}

int main() 
{
  std::map<int, int> map = { { 1, 2 }, { 2, 3 } };
  std::cout << *map.begin() << std::endl;//This works

  std::copy(
    map.begin(),
    map.end(),
    std::ostream_iterator<std::pair<int,int> >(std::cout, " ")
  ); //this doesn't work
}

produces an error

no match for ‘operator<<’ (operand types are ‘std::ostream_iterator<std::pair<int, int> >::ostream_type {aka std::basic_ostream<char>}’ and ‘const std::pair<int, int>’)

I guess this isn't working because my overload isn't available inside std::copy, but why is that?

like image 204
Roger Rodriguez Texido Avatar asked Jun 08 '14 21:06

Roger Rodriguez Texido


1 Answers

Explanation

Since operator<< is being called in an unqualified manner from inside namespace std (more specifically inside std::ostream_iterator), and all the arguments involved are also declared in the same namespace, only namespace std will be searched for potential matches.


Hackish solution

namespace std {
  template<typename t1, typename t2>
  std::ostream& operator<<(std::ostream& os, const std::pair<t1, t2>& pair)
  {
     return os << "< " << pair.first << " , " << pair.second << " >";
  }
}

Note: You can only specialize templates that includes user-defined types inside namespace std, the above snippet is therefore potentially ill-formed according to the Standard (if std::pair<T1,T2> isn't a user-declared type, see this discussion).


Detailed explanation

Below we have namespace N which will aid us in trying to simulate your usage of namespace std, and what is going on when the compiler tries to find a suitable overload for a given type.

namespace N

namespace N {
  struct A { };
  struct B { };

  void func (A value) { std::cout << "A"; }

  template<class T>
  void call_func (T value) { func (value); }
}

main.cpp

void func (N::B value) {
  std::cout << "B";
}

int main() {
  N::A a;
  N::B b;

  func (a);         // (1)
  func (b);         // (2)

  N::call_func (a); // (3a)
  N::call_func (b); // (3b)
}

Notes:

  1. Without knowing about Argument-dependent lookup, one might be surprised that the compiler is able to find the suitable overload required to make (1) work.

    ADL states that upon using an unqualified-name in a function call, not only is the current namespace searched for suitable overloads, the namespace of the arguments are also searched; and this is how the compiler finds N::func, even though we didn't write so explicitly.

  2. We have a suitable overload in the current namespace; it's all good in the hood.

  3. ...

    Why does (3a) compile, whereas (3b) will result in a nasty diagnostic?

    When we instantiate the template N::call_func<T> it will try to pass on the argument of type T to the unqualified function named func.

    Since the rules of name-lookup says that the current namespace, and the namespace of the arguments involved, are searched for suitable matches in case we are calling a function from a unqualified name, it will only search namespace N if T is a type declared in namespace N.

    Both N::A and N::B are declared in namespace N, so the compiler will not search any other scope to find a suitable overload; which is why lookup fails.

like image 168
Filip Roséen - refp Avatar answered Sep 21 '22 05:09

Filip Roséen - refp