3

The following code works, but i would like to move ostream& operator<< outside of the class declearation as i have with hash::operator[].

#include<iostream>
#include<map>

using namespace std;

template <class T>
class hash {
  private:
    map<string, T> _map;
  public:
    T& operator[] (string x);
    friend ostream& operator<<(ostream& out, const hash<T> &rhs) { return out << "test"; }
};

template <class T>
T & hash<T>::operator[](string x) {
  return _map[x];
}

int main () {
  hash<int> myobject;
  myobject["a"] = 1;
  cout << myobject["a"] << endl;
  cout << myobject << endl;
  return 0;
}

I have tried:

template <class T>
ostream& operator<<(...) {
  return out << "test";
}

and

ostream& operator<<(...) {
  return out << "test";
}

as well as a few other combinations to no avail.

everett1992
  • 2,351
  • 3
  • 27
  • 38
  • See [this](http://stackoverflow.com/questions/4147399/template-class-friend-operator-overload) – EdChum Mar 17 '12 at 23:13
  • possible duplicate of [overloading friend operator<< for template class](http://stackoverflow.com/questions/4660123/overloading-friend-operator-for-template-class) (I wrote a quite descriptive answer with the different options [there](http://stackoverflow.com/a/4661372/36565) that you might want to go over). – David Rodríguez - dribeas Mar 17 '12 at 23:34

2 Answers2

6

Since it does not seem that this question is getting closed as exact duplicate, I will explain what your program does.

template <typename T>
class test {
   int private_field;
   friend std::ostream& operator<<( std::ostream&, test<T> const & );
};
// ???
int main() {
   std::cout << test<int>() << std::endl;
}

Templates are instantiated on demand (unless you explicitly instantiate them), that means that in this particular program test is instantiated only as test<int>. When the compiler instantiates the template (because it was requested in main) it will process the template definition. At this point the behavior is similar to rewriting the code with the substituted type at the point of definition (in this case right before main):

class test<int> {
   friend std::ostream& operator<<( std::ostream&, test<int> const & );
};

Now, if you look at the instantiated template, you can note that the friend declaration is befriending a non templated function. So in this particular program you can fill in the ??? with that particular function:

std::ostream& operator<<( std::ostream& o, test<int> const & t ) {
   return o << t.private_field;
}

The problem here is that this is not easily extensible. The code of that operator<< is the same for test<int> than it is for test<double>, so there should be no need to rewrite the same function for all instantiating types!

At this point there are two options, the first one is, as you have already identified, providing the definition of the function inside the class. The compiler will then process and define the function whenever the type is being instantiated. This solution creates non-templated functions on demand for each template instantiation (which is the option I would do, even with the quirks and strangeness of lookup here).

Now, if you really want to avoid providing the definition inside the templates class, and you still want to provide a single implementation, then you have to resort to providing a templates operator<<. At this point you have two different options, you can declare all instantiations of the template (which I don't quite like, as it opens to too many others), or you can befriend a single specialization of the template function (cleaner regarding access, more cumbersome to write).

The first case is:

template <typename T>
class test {
   template <typename U>
   friend std::ostream& operator<<( std::ostream&, test<U> const & );
};
template <typename T>
std::ostream& operator<<( std::ostream&, test<T> const & ) { ... }

The second case requires a couple of forward declarations:

template <typename T> test;
template <typename T> std::ostream& operator<<( std::ostream&, test<T> const & );
template <typename T>
class test {
   friend std::ostream& operator<< <T>( std::ostream&, const test<T>& );
};
template <typename T>
std::ostream& operator<<( std::ostream&, test<T> const & ) { ... }

There is of course another option: do not declare friends at all. Provide a print(std::ostream&) public function in your class that has the implementation, and provide a non-templated operator<< that just calls print on the second argument.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
3

You have to be careful when you use friend and template

#include<iostream>
#include<map>

template <class T>
class MyHash{
public:
    // ... use your T template here
    template <class U>
    friend ostream& operator<<(ostream& out, const MyHash<U> &rhs);
};

// ...
template <class U>
ostream& operator<<(ostream& out, const MyHash<U> &rhs){ return out << "test"; }

you need to use a different template U because operator<< is a friend method (external), if we use T instead of U, we will have : ambiguous definition in template .

Change hash by MyHash to avoid ambiguities.

Edit :

The error that you get here is :

error C2676: '<' binaire : 'std::string' ne définit pas cet opérateur ou une conversion vers un type acceptable pour l'opérateur prédéfini

because you forgot to include <string> in "hash.h". That header defines the < operator.

And also try moving the definition of operator[] and operator<< directly into "hash.h" Both the declaration and definition of templates must be included. This is because in some compilers template functions cannot be compiled and linked independently, since they are generated on request for the specific types they are instantiated with.

Change the #include "hash.cpp" to #include "hash.h"

Ouadie
  • 13,005
  • 4
  • 52
  • 62
  • I have a follow up question: when I use your solution, and I place the function definition in a different file than the class decleration I need to include the .cpp file with the definition, and not the .h file when i try and use that class. What am I doing wrong? – everett1992 Mar 19 '12 at 19:09
  • [here](https://github.com/everett1992/couple-solver/tree/master/test_dir) is an example of what I'm talking about. If I were to compile this as it is everything is ok, but if i change the include hash.cpp to hash.h in test.cpp, and compile with g++ test.cpp hash.cpp then it doesnt compile – everett1992 Mar 19 '12 at 19:16
  • briefly include in "hash.h", or read the Edit part in my answer for more details. – Ouadie Mar 20 '12 at 00:17