嵌套结构的运算符重载仅作为成员或友元函数
Operator overloading for nested struct only working as member or friend function
这段 C++ 代码编译和运行完美,如我所料:
template <typename T> struct S { T *p; };
template <typename T>
bool operator == (S<T> &a, S<T> &b) { return a.p == b.p; }
int main () { int i; S<int> a = {&i}, b = {&i}; return a == b; }
但是,如果我尝试对外部结构的内部结构执行相同的操作...
template <typename T> struct O { struct I {T *p;}; };
template <typename T>
bool operator == (O<T>::I &a, O<T>::I &b) { return a.p == b.p; }
int main () { int i; O<int>::I a = {&i}, b = {&i}; return a == b; }
...然后它不再编译(gcc 版本 8.3.0,Debian GNU/Linux 10):
1.cpp:4:25: error: declaration of ‘operator==’ as non-function
bool operator == (O<T>::I &a, O<T>::I &b) { return a.p == b.p; }
^
[...]
为什么会这样?我也看不懂上面的错误提示
请注意,我知道我可以通过内部结构的 defining the operator as a member function 使其工作:
template <typename T>
struct O2 {
struct I2 {
T *p;
bool operator == (I2 &b) { return p == b.p; }
};
};
int main () { int i; O2<int>::I2 a = {&i}, b = {&i}; return a == b; }
但是,如果可能的话,我宁愿使用非成员函数版本,因为我发现它更对称,因此更清晰。
此外,部分通过反复试验,我发现以下对称版本有效...
template <typename T>
struct O3 {
struct I3 { T *p; };
friend bool operator == (I3 &a, I3 &b) { return a.p == b.p; }
};
int main () { int i; O3<int>::I3 a = {&i}, b = {&i}; return a == b; }
...但是我真的不明白上面发生了什么。首先,考虑到 friend declaration“授予一个函数或另一个 class 访问出现友元声明的 class 的私有和受保护成员”,我不明白它在上面的代码,假设我们总是处理结构,因此处理 public 成员。
其次,如果我删除 friend
说明符,则它不再编译。此外,[...] operator== [...] must have exactly one argument
错误消息让我认为在这种情况下,编译器希望我定义一个成员函数 operator==
,其左操作数是 O3
,而不是 I3
。然而,显然 friend
说明符改变了这种情况; 为什么会这样?
首先,编译器因缺少 typename
而感到困惑。错误消息确实令人困惑,可以通过以下方式消除:
template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) {
return a.p == b.p;
}
现在实际问题变得更加明显:
int main () {
int i;
O<int>::I a = {&i}, b = {&i};
return a == b;
}
导致错误:
<source>: In function 'int main()':
<source>:15:14: error: no match for 'operator==' (operand types are 'O<int>::I' and 'O<int>::I')
15 | return a == b;
| ~ ^~ ~
| | |
| | I<[...]>
| I<[...]>
<source>:8:6: note: candidate: 'template<class T> bool operator==(typename O<T>::I&, typename O<T>::I&)'
8 | bool operator == (typename O<T>::I &a, typename O<T>::I &b) {
| ^~~~~~~~
<source>:8:6: note: template argument deduction/substitution failed:
<source>:15:17: note: couldn't deduce template parameter 'T'
15 | return a == b;
| ^
不可能从a == b
推导出T
,(@dfribs的话)
because T
is in a non-deduced context in both of the function
parameters of the operator function template; T
cannot be deduced
from O<T>::I&
, meaning T
cannot be deduced from any of the
arguments to the call (and function template argument deduction
subsequently fails).
说的马虎,因为O<S>::I
可以和O<T>::I
一样,即使S != T
.
当运算符被声明为成员时,只有一个候选者将O<T>::I
与另一个进行比较(因为运算符本身不是模板,即不需要推导)。
如果您想将运算符实现为非成员,我建议不要在 O
:
中定义 I
template <typename T>
struct I_impl {
T *p;
};
template <typename T>
bool operator == (I_impl<T> &a,I_impl<T> &b) {
return a.p == b.p;
}
template <typename T>
struct O {
using I = I_impl<T>;
};
int main () {
int i;
O<int>::I a = {&i}, b = {&i};
return a == b;
}
您对 friend
的困惑与运算符重载有些无关。考虑:
#include <iostream>
void bar();
struct foo {
friend void bar(){ std::cout << "1";}
void bar(){ std::cout << "2";}
};
int main () {
bar();
foo{}.bar();
}
输出:
12
我们对 foo
中的 bar
有两个定义。 friend void bar(){ std::cout << "1";}
将自由函数 ::bar
(已在全局范围内声明)声明为 foo
的友元并定义它。 void bar(){ std::cout << "2";}
声明(并定义)一个名为 bar
的 foo
成员:foo::bar
.
回到operator==
,考虑a == b
是一种更短的写法
a.operator==(b); // member ==
或
operator==(a,b); // non member ==
成员方法获取 this
指针作为传递的隐式参数,自由函数则不然。这就是为什么 operator==
必须将一个参数作为成员,将两个参数作为自由函数,这是错误的:
struct wrong {
bool operator==( wrong a, wrong b);
};
虽然这是正确的:
struct correct {
bool operator==(wrong a);
};
struct correct_friend {
friend operator==(wrong a,wrong b);
};
编译C++不需要编译器解决停机问题。
template <typename T> struct O { struct I {T *p;}; };
template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) { return a.p == b.p; }
从 O<int>::I
到 int
需要,在一般情况下,编译器解决暂停问题。证明原因:
template <typename T> struct O { struct I {T *p;}; };
template <> struct O<double> { using I=O<int>::I; };
template <> struct O<char> { using I=std::string; };
template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) { return a.p == b.p; }
现在,O<int>::I
可以命名为O<double>::I
。您实际上可以使从 O<T>::I
到 T
的映射需要反转任意图灵完备函数,因为模板特化是图灵完备的。
在进行参数的模板模式匹配时,C++ 并没有划出一个可反转的依赖类型映射区域,而是简单地说“不要反转依赖类型”。
所以
template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) { return a.p == b.p; }
将永远不会推断T
。
现在您可以完成这项工作,但它需要您定义逆向映射而不依赖于模板类型推导。
到目前为止,最简单的方法是在 O
之外定义 I
。如果做不到这一点,您需要定义一种方法来查找给定 O<T>::I
的 O<T>
,例如:
template <typename T> struct O {
struct I {using Outer=O<T>;T *p;};
};
template<class Inner>
using Outer=typename Inner::Outer;
template<class X>
struct Type0;
template<template<class...>class Z, class T0, class...Ts>
struct Type0<Z<T0,Ts...>>{ using type=T0; };
template<class X>
using Type0_t=typename Type0<X>::type;
然后我们可以
template <class I> requires (std::is_same_v<I, typename O<Type0_t<Outer<Inner>>>::I>)
bool operator == (Inner const &a, Inner const &b) { return a.p == b.p; }
并且您的代码有效。
这段 C++ 代码编译和运行完美,如我所料:
template <typename T> struct S { T *p; };
template <typename T>
bool operator == (S<T> &a, S<T> &b) { return a.p == b.p; }
int main () { int i; S<int> a = {&i}, b = {&i}; return a == b; }
但是,如果我尝试对外部结构的内部结构执行相同的操作...
template <typename T> struct O { struct I {T *p;}; };
template <typename T>
bool operator == (O<T>::I &a, O<T>::I &b) { return a.p == b.p; }
int main () { int i; O<int>::I a = {&i}, b = {&i}; return a == b; }
...然后它不再编译(gcc 版本 8.3.0,Debian GNU/Linux 10):
1.cpp:4:25: error: declaration of ‘operator==’ as non-function
bool operator == (O<T>::I &a, O<T>::I &b) { return a.p == b.p; }
^
[...]
为什么会这样?我也看不懂上面的错误提示
请注意,我知道我可以通过内部结构的 defining the operator as a member function 使其工作:
template <typename T>
struct O2 {
struct I2 {
T *p;
bool operator == (I2 &b) { return p == b.p; }
};
};
int main () { int i; O2<int>::I2 a = {&i}, b = {&i}; return a == b; }
但是,如果可能的话,我宁愿使用非成员函数版本,因为我发现它更对称,因此更清晰。
此外,部分通过反复试验,我发现以下对称版本有效...
template <typename T>
struct O3 {
struct I3 { T *p; };
friend bool operator == (I3 &a, I3 &b) { return a.p == b.p; }
};
int main () { int i; O3<int>::I3 a = {&i}, b = {&i}; return a == b; }
...但是我真的不明白上面发生了什么。首先,考虑到 friend declaration“授予一个函数或另一个 class 访问出现友元声明的 class 的私有和受保护成员”,我不明白它在上面的代码,假设我们总是处理结构,因此处理 public 成员。
其次,如果我删除 friend
说明符,则它不再编译。此外,[...] operator== [...] must have exactly one argument
错误消息让我认为在这种情况下,编译器希望我定义一个成员函数 operator==
,其左操作数是 O3
,而不是 I3
。然而,显然 friend
说明符改变了这种情况; 为什么会这样?
首先,编译器因缺少 typename
而感到困惑。错误消息确实令人困惑,可以通过以下方式消除:
template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) {
return a.p == b.p;
}
现在实际问题变得更加明显:
int main () {
int i;
O<int>::I a = {&i}, b = {&i};
return a == b;
}
导致错误:
<source>: In function 'int main()':
<source>:15:14: error: no match for 'operator==' (operand types are 'O<int>::I' and 'O<int>::I')
15 | return a == b;
| ~ ^~ ~
| | |
| | I<[...]>
| I<[...]>
<source>:8:6: note: candidate: 'template<class T> bool operator==(typename O<T>::I&, typename O<T>::I&)'
8 | bool operator == (typename O<T>::I &a, typename O<T>::I &b) {
| ^~~~~~~~
<source>:8:6: note: template argument deduction/substitution failed:
<source>:15:17: note: couldn't deduce template parameter 'T'
15 | return a == b;
| ^
不可能从a == b
推导出T
,(@dfribs的话)
because
T
is in a non-deduced context in both of the function parameters of the operator function template;T
cannot be deduced fromO<T>::I&
, meaningT
cannot be deduced from any of the arguments to the call (and function template argument deduction subsequently fails).
说的马虎,因为O<S>::I
可以和O<T>::I
一样,即使S != T
.
当运算符被声明为成员时,只有一个候选者将O<T>::I
与另一个进行比较(因为运算符本身不是模板,即不需要推导)。
如果您想将运算符实现为非成员,我建议不要在 O
:
I
template <typename T>
struct I_impl {
T *p;
};
template <typename T>
bool operator == (I_impl<T> &a,I_impl<T> &b) {
return a.p == b.p;
}
template <typename T>
struct O {
using I = I_impl<T>;
};
int main () {
int i;
O<int>::I a = {&i}, b = {&i};
return a == b;
}
您对 friend
的困惑与运算符重载有些无关。考虑:
#include <iostream>
void bar();
struct foo {
friend void bar(){ std::cout << "1";}
void bar(){ std::cout << "2";}
};
int main () {
bar();
foo{}.bar();
}
输出:
12
我们对 foo
中的 bar
有两个定义。 friend void bar(){ std::cout << "1";}
将自由函数 ::bar
(已在全局范围内声明)声明为 foo
的友元并定义它。 void bar(){ std::cout << "2";}
声明(并定义)一个名为 bar
的 foo
成员:foo::bar
.
回到operator==
,考虑a == b
是一种更短的写法
a.operator==(b); // member ==
或
operator==(a,b); // non member ==
成员方法获取 this
指针作为传递的隐式参数,自由函数则不然。这就是为什么 operator==
必须将一个参数作为成员,将两个参数作为自由函数,这是错误的:
struct wrong {
bool operator==( wrong a, wrong b);
};
虽然这是正确的:
struct correct {
bool operator==(wrong a);
};
struct correct_friend {
friend operator==(wrong a,wrong b);
};
编译C++不需要编译器解决停机问题。
template <typename T> struct O { struct I {T *p;}; };
template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) { return a.p == b.p; }
从 O<int>::I
到 int
需要,在一般情况下,编译器解决暂停问题。证明原因:
template <typename T> struct O { struct I {T *p;}; };
template <> struct O<double> { using I=O<int>::I; };
template <> struct O<char> { using I=std::string; };
template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) { return a.p == b.p; }
现在,O<int>::I
可以命名为O<double>::I
。您实际上可以使从 O<T>::I
到 T
的映射需要反转任意图灵完备函数,因为模板特化是图灵完备的。
在进行参数的模板模式匹配时,C++ 并没有划出一个可反转的依赖类型映射区域,而是简单地说“不要反转依赖类型”。
所以
template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) { return a.p == b.p; }
将永远不会推断T
。
现在您可以完成这项工作,但它需要您定义逆向映射而不依赖于模板类型推导。
到目前为止,最简单的方法是在 O
之外定义 I
。如果做不到这一点,您需要定义一种方法来查找给定 O<T>::I
的 O<T>
,例如:
template <typename T> struct O {
struct I {using Outer=O<T>;T *p;};
};
template<class Inner>
using Outer=typename Inner::Outer;
template<class X>
struct Type0;
template<template<class...>class Z, class T0, class...Ts>
struct Type0<Z<T0,Ts...>>{ using type=T0; };
template<class X>
using Type0_t=typename Type0<X>::type;
然后我们可以
template <class I> requires (std::is_same_v<I, typename O<Type0_t<Outer<Inner>>>::I>)
bool operator == (Inner const &a, Inner const &b) { return a.p == b.p; }
并且您的代码有效。