简体   繁体   中英

How do you declare the friend of a generic class outside of the class decleration?

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.

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 friend s 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.

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"

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