首页 > 解决方案 > 在模板类中声明与朋友相同的模板类?

问题描述

考虑以下:

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;
  }
};

该类Integer定义友operator==元来比较具有不同模板参数的其他实体,并将模板Integer类声明为友元类。

但是当我比较两个具有不同模板参数的对象时:

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

Clang 接受它,但 GCC 和 MSVC 拒绝它

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

这让我有点吃惊。在我看来,它应该是格式良好的,因为operator==是 的朋友Integer<int>,并且Integer<long>是 的朋友Integer<int>,所以operator==也应该有访问 的成员的权限Integer<long>

那么我应该信任哪个编译器?

更新

这是CWG1699

标签: c++templateslanguage-lawyerc++20friend

解决方案


[班级.朋友]

10友谊既不是遗传的,也不是传递的。[示例 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
  }
};

——结束示例]

实例化Integer<int>“注入”以下函数:

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

现在,虽然Integer<int>可能是 的朋友Integer<long>,但operator==添加的不是。最多只是一个朋友Integer<int>。所以它不能访问Integer<long>. 当然,同样的分析也适用于另一个方向(这并不重要,因为重写的候选人被认为是较差的匹配1)。

如果你想要混合比较,你需要对称。两个参数都必须是未知Integer的特化(带有自己的模板参数),并且类模板需要与操作符友好。

不幸的是,这也意味着不能内联定义运算符(因为每个实例化都会“注入”并重新定义它)。所以这是可行的解决方案:

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 - “......如果...... F2是一个重写的候选者([over.match.oper])和F1 不是”


推荐阅读