This C++ code compiles and runs perfectly, as I expect:
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; }
However, if I try to do the same with the inner struct of an outer struct...
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; }
... then it doesn't compile anymore (gcc version 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; }
^
[...]
Why is it so? I also do not understand the above error message.
Note that I'm aware that I can make it work by defining the operator as a member function of the inner struct:
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; }
However, if somehow possible, I'd rather use the non-member function version, because I find it more symmetric and therefore clearer.
Also, partly by trial and error, I found that the following symmetric version works...
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; }
... but I do not really understand what is happening above. First, given that a friend declaration "grants a function or another class access to private and protected members of the class where the friend declaration appears", I do not understand how it helps in the code above, given that we're always dealing with structs and therefore with public members.
Second, if I remove the friend
specifier, then it doesn't compile anymore. Also, the [...] operator== [...] must have exactly one argument
error message makes me think that in this case the compiler expects me to define a member function operator==
whose left operand is O3
, not I3
. Apparently, however, the friend
specifier changes this situation; why is it so?
First, the compiler gets confused by missing typename
. The error message really is confusing and can be silenced via:
template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) {
return a.p == b.p;
}
Now the actual problem gets more apparent:
int main () {
int i;
O<int>::I a = {&i}, b = {&i};
return a == b;
}
results in the error:
<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;
| ^
It is not possible to deduce T
from a == b
, (@dfribs words)
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).
Sloppy speaking, because O<S>::I
could be the same as O<T>::I
, even if S != T
.
When the operator is declared as member then there is only one candidate to compare a O<T>::I
with another (because the operator itself is not a template, ie no deduction needed).
If you want to implement the operator as non member I would suggest to not define I
inside O
:
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;
}
Your confusion about friend
is somewhat unrelated to operator overloading. Consider:
#include <iostream>
void bar();
struct foo {
friend void bar(){ std::cout << "1";}
void bar(){ std::cout << "2";}
};
int main () {
bar();
foo{}.bar();
}
Output:
12
We have two definitions for a bar
in foo
. friend void bar(){ std::cout << "1";}
declares the free function ::bar
(already declared in global scope) as a friend of foo
and defines it. void bar(){ std::cout << "2";}
declares (and defines) a member of foo
called bar
: foo::bar
.
Back to operator==
, consider that a == b
is a shorther way of writing either
a.operator==(b); // member ==
or
operator==(a,b); // non member ==
Member methods get the this
pointer as implicit parameter passed, free functions not. Thats why operator==
must take exactly one parameter as member and exactly two as free function and this is wrong:
struct wrong {
bool operator==( wrong a, wrong b);
};
while this is correct:
struct correct {
bool operator==(wrong a);
};
struct correct_friend {
friend operator==(wrong a,wrong b);
};