Declare same template class as friend inside the template class?

Viewed 97

Consider the following:

template<std::integral T>
class Integer {
  T value;

  template<std::integral T2>
  friend class Integer;

 public:
  template<std::integral T2>
  friend bool operator==(Integer<T> x, Integer<T2> y) {
    return x.value == y.value;
  }
};

the Integer class defines the friend operator== to compare other entities with different template parameters and declares the template Integer class as the friend class.

But when I compare two objects with different template parameters:

Integer<int> x;
Integer<long> y;
x == y;

Clang accepts it but GCC and MSVC reject it with:

<source>:11:25: error: 'long int Integer<long int>::value' is private within this context
   11 |     return x.value == y.value;
      |                       ~~^~~~~

This surprised me a bit. In my opinion, it should be well-formed, because operator== is a friend of Integer<int>, and Integer<long> is a friend of Integer<int>, so operator== should also have permission to access the member of Integer<long>.

So Which compiler should I trust?

Update

This is CWG1699.

2 Answers

[class.friend]

10 Friendship is neither inherited nor transitive. [Example 8:

class A {
  friend class B;
  int a;
};

class B {
  friend class C;
};

class C  {
  void f(A* p) {
    p->a++;         // error: C is not a friend of A despite being a friend of a friend
  }
};

class D : public B  {
  void f(A* p) {
    p->a++;         // error: D is not a friend of A despite being derived from a friend
  }
};

— end example]

Instantiating Integer<int> "injects" the following function:

template<std::integral T2>
  friend bool operator==(Integer<int> x, Integer<T2> y)

Now, while Integer<int> could be a friend of Integer<long>, the operator== that gets added is not. It's only a friend of Integer<int> at most. So it cannot access a private member of Integer<long>. The same analysis works in the other direction too, of course (not that it matters much, since a rewritten candidate is considered a poorer match1).

If you want mixed comparisons, you need symmetry. Both arguments must be unknown Integer specializations (with their own template parameter), and the class template needs to befriend the operator.

Unfortunately, that also means that the operator can't be defined inline (since every instantiation will "inject" and redefine it). So this is the viable solution:

template<std::integral T>
class Integer {
  T value{};

 public:
    template<std::integral T2, std::integral T3>
    friend bool operator==(Integer<T2> x, Integer<T3> y);
};

template<std::integral T2, std::integral T3>
bool operator==(Integer<T2> x, Integer<T3> y) {
    return x.value == y.value;
}

1 over.match.best.general/2.8 - "... a viable function F1 is defined to be a better function than another viable function F2 if ... F2 is a rewritten candidate ([over.match.oper]) and F1 is not"

The friend of your friend is not your friend.

#include <concepts>

template<std::integral T>
class Integer {
  T value;

  template<std::integral T2>
  friend class Integer;

 public:
  template<std::integral T2>
  bool operator==(Integer<T2> y) const {
    return value == y.value;
  }
};

int main() {
  Integer<int> x;
  Integer<long> y;
  return x == y;
}

this works, as the member operator== is befriended.

Another approach is using a private subclass accessor "key" object:

struct GetValue {
  template<std::integral T2>
  T operator()(Integer<T2> const& self)const{
    return self.value;
  }
};

as a member class, it does gain access to the friendship of its containing class.

Once we have this, then operator== is simple and can remain a free friend function:

template<std::integral T2>
friend bool operator==(Integer<T> x, Integer<T2> y) {
  return GetValue{}(x) == GetValue{}(y);
}

the ability to make GetValue "key" objects, which operator== has, gives the ability to read the value of different Integer<T2>s via the GetValue object.

This isn't a great answer; @storyteller did a good job there. This is merely two clean techniques to solve the problem.

Related