views:

57

answers:

2

I am trying to write a class template that provides a comparison operator between two instatiations with different template types. As is often the case, this operator is a non-member friend. A simplified example of what I am trying to achieve can be seen below.

template<typename T>
class Wrapper {
  // Base type, which must have a val() method to allow comparison
  T t_;
public:
  Wrapper(const T& t) : t_(t) {}

  // Comparison operator
  template<typename B>
  friend
  bool
  operator==(const Wrapper &a, const Wrapper<B>&b) {
    return a.t_.val()==b.t_.val();
  }
};

// First example type for Wrapper
class X {
  int x_;    
public:
  X(int x) : x_(x) {}

  int
  val() const {return x_;}
};

// Second example type for Wrapper
class Y {
  int y_;
public:
  Y(int y) : y_(y) {}

  int
  val() const {return 2*y_;}
};

int
main() {
  Wrapper<X> WX(X(4));
  Wrapper<Y> WY(Y(2));
  return WX==WY ? 1 : 0;
}

This example (g++ 4.4.0) doesn't compile: instead it complains that y_ from Wrapper<Y> is private and so inaccessible to the friend function, and I can see why. But how can I fix this? Adding friendship to the reversed function

  template<typename B>
  friend bool operator==(const Wrapper<B> &, const Wrapper &);

into the Wrapper class template body merely causes ambiguity to the compiler. I don't want to allow different instatiations of the Wrapper class to have access to the private members of each other - I want to restrict access to this one operator. Is this possible?

My laptop's in peril from being defenestrated, so any ideas would be appeciated by both me and the laptop (and the window for that matter).

+2  A: 

My laptop is safe (for now) as re-reading C++ FAQ-lite turned out to help, although the example didn't initially appear to match my own problem. The following does the job, by moving the definition of the operator outside the template class body:

template <typename T> class Wrapper;

template<typename A, typename B>
inline
bool
operator==(const Wrapper<A> &a, const Wrapper<B>&b) {return a.t_.val()==b.t_.val();}

template<typename T>
class Wrapper {
  T t_;
public:
  Wrapper(const T& t) : t_(t) {}

  template<typename A, typename B>
  friend
  bool
  operator==(const Wrapper<A> &a, const Wrapper<B>&b);
};

Any more elegant or insightful suggestions would be appreciated.

beldaz
For more insight, read this [answer](http://stackoverflow.com/questions/1810753/overloading-operator-for-a-templated-class/1811208#1811208) that I provided to a different question, but that was a problem with template friendship.
David Rodríguez - dribeas
@dribeas - Thanks (and +1) for the link. Very handy. I wish SO allowed us to tag favourite answers as well as favourite questions.
beldaz
+2  A: 

Number of things:

  • When your class template is instantiated once for X and once for Y, you have two definitions of the operator==.

  • Move the operator== out of the class declaration.

  • Note that a friend is not a member. Hence the access violation related diagnostic.

Try this:

template<typename T>
class Wrapper {
  // Base type, which must have a val() method to allow comparison
  T t_;
public:
  Wrapper(const T& t) : t_(t) {}

  // Comparison operator

  template<typename A, typename B>
  friend bool
  operator==(const Wrapper<A> &a, const Wrapper<B>&b);
};

template<typename A, typename B>
  bool
  operator==(const Wrapper<A> &a, const Wrapper<B>&b) {
    return a.t_.val()==b.t_.val();
  }

// First example type for Wrapper
class X {
  int x_;    
public:
  X(int x) : x_(x) {}

  int
  val() const {return x_;}
};

// Second example type for Wrapper
class Y {
  int y_;
public:
  Y(int y) : y_(y) {}

  int
  val() const {return 2*y_;}
};

int
main() {
  Wrapper<X> WX(X(4));
  Wrapper<Y> WY(Y(2));
  return ::operator==(WX, WY) ? 1 : 0;
}

Though I still don't like the possibility that there are two possible friend operator== lurking in there...

dirkgently
Looks like you reached the same conclusion I did, which is encouraging for me. Is your suspicion of two possible operator== well grounded? Wouldn't the compiler complain of an ambiguous function call if that were the case?
beldaz
The arguments would be in reverse order if you have `WX == WY; WY == WX`. Of course, a third type (say 'Z') involves another set of possibly non conflicting `op==` definitions and so on.
dirkgently
Why do you say that there are two possible `friend operator==`? The friend declaration will be encountered more than once by the compiler, but they declare a single unique template as friend that will only be instantiated once. The two argument `operator==` will be the friend of every instantiation of `Wrapper`, but there is a single such template. I don't quite remember completely, but it might be the case that you need to forward declare the template before the `Wrapper` definition, IIRC.
David Rodríguez - dribeas
Templates are supposed to be instantiated based on the permutations of argument types (or values if used as template parameters)... nothing inherently wrong with that, and it's typically great for small functions (insignificant bloat). When problematic there are alternatives (e.g. casting to a single comparable type).
Tony
@dribeas: This was w.r.t OP's code: Template instantiation for `X` declares and defines: `template<B> bool operator==(const Wrapper<X> ` and that for `Y`: `template<B> bool operator==(const Wrapper<Y> `. Wouldn't they be different?
dirkgently
@dirkgently: I think our answers are essentially identical, so I'll take yours. Many thanks.
beldaz
@beldaz: Yes. More than welcome :) (You could accept yours too, I think!)
dirkgently