在模板 class 中声明与朋友相同的模板 class?
Declare same template class as friend inside the template class?
考虑以下几点:
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
class 定义友元 operator==
来比较具有不同模板参数的其他实体,并将模板 Integer
class 声明为友元 class.
但是当我比较两个具有不同模板参数的对象时:
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;
| ~~^~~~~
这让我有点吃惊。在我看来,它应该是合式的,因为 operator==
是 Integer<int>
的朋友,而 Integer<long>
是 Integer<int>
的朋友,所以 operator==
应该也有权限访问 Integer<long>
.
的成员
那么我应该信任哪个编译器?
更新
这是CWG1699。
[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]
实例化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
特化(具有它们自己的模板参数),并且 class 模板需要与运算符友好。
不幸的是,这也意味着无法内联定义运算符(因为每个实例化都会“注入”并重新定义它)。所以这是可行的解决方案:
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 - "...一个可行的函数 F1 被定义为比另一个可行的函数 F2 更好的函数,如果... F2 是一个重写的候选者([over.match.oper]) 而 F1 不是
你朋友的朋友不是你的朋友。
#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;
}
这有效,因为成员 operator==
已成为朋友。
另一种方法是使用私有子class访问器“键”对象:
struct GetValue {
template<std::integral T2>
T operator()(Integer<T2> const& self)const{
return self.value;
}
};
作为成员 class,它 确实 获得了其包含 class.
的友谊
一旦我们有了这个,那么 operator==
就很简单了,可以保留一个免费的朋友功能:
template<std::integral T2>
friend bool operator==(Integer<T> x, Integer<T2> y) {
return GetValue{}(x) == GetValue{}(y);
}
制作GetValue
“关键”对象的能力,operator==
拥有,能够通过GetValue
对象读取不同Integer<T2>
的值.
这不是一个很好的 language-lawyer 答案; @storyteller 在那里做得很好。这只是解决问题的两个干净的技术。
考虑以下几点:
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
class 定义友元 operator==
来比较具有不同模板参数的其他实体,并将模板 Integer
class 声明为友元 class.
但是当我比较两个具有不同模板参数的对象时:
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;
| ~~^~~~~
这让我有点吃惊。在我看来,它应该是合式的,因为 operator==
是 Integer<int>
的朋友,而 Integer<long>
是 Integer<int>
的朋友,所以 operator==
应该也有权限访问 Integer<long>
.
那么我应该信任哪个编译器?
更新
这是CWG1699。
[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]
实例化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
特化(具有它们自己的模板参数),并且 class 模板需要与运算符友好。
不幸的是,这也意味着无法内联定义运算符(因为每个实例化都会“注入”并重新定义它)。所以这是可行的解决方案:
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 - "...一个可行的函数 F1 被定义为比另一个可行的函数 F2 更好的函数,如果... F2 是一个重写的候选者([over.match.oper]) 而 F1 不是
你朋友的朋友不是你的朋友。
#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;
}
这有效,因为成员 operator==
已成为朋友。
另一种方法是使用私有子class访问器“键”对象:
struct GetValue {
template<std::integral T2>
T operator()(Integer<T2> const& self)const{
return self.value;
}
};
作为成员 class,它 确实 获得了其包含 class.
的友谊一旦我们有了这个,那么 operator==
就很简单了,可以保留一个免费的朋友功能:
template<std::integral T2>
friend bool operator==(Integer<T> x, Integer<T2> y) {
return GetValue{}(x) == GetValue{}(y);
}
制作GetValue
“关键”对象的能力,operator==
拥有,能够通过GetValue
对象读取不同Integer<T2>
的值.
这不是一个很好的 language-lawyer 答案; @storyteller 在那里做得很好。这只是解决问题的两个干净的技术。