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?
2
Answers
First, the compiler gets confused by missing
typename
. The error message really is confusing and can be silenced via:Now the actual problem gets more apparent:
results in the error:
It is not possible to deduce
T
froma == b
, (@dfribs words)Sloppy speaking, because
O<S>::I
could be the same asO<T>::I
, even ifS != 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
insideO
:Your confusion about
friend
is somewhat unrelated to operator overloading. Consider:Output:
We have two definitions for a
bar
infoo
.friend void bar(){ std::cout << "1";}
declares the free function::bar
(already declared in global scope) as a friend offoo
and defines it.void bar(){ std::cout << "2";}
declares (and defines) a member offoo
calledbar
:foo::bar
.Back to
operator==
, consider thata == b
is a shorther way of writing eitheror
Member methods get the
this
pointer as implicit parameter passed, free functions not. Thats whyoperator==
must take exactly one parameter as member and exactly two as free function and this is wrong:while this is correct:
Compiling C++ does not require compilers to solve the halting problem.
going from
O<int>::I
toint
requires, in the general case, that the compiler solve the halting problem. To demonstrate why:now,
O<int>::I
can be namedO<double>::I
. You can actually make the map fromO<T>::I
toT
require inverting an arbitrary turing-complete function, as template specialization is turing-complete.Rather than carving out a region of invertable dependent type maps, C++ simply says "do not invert dependent types" when doing template pattern matching of arguments.
So
will never deduce
T
.Now you can make this work, but it requires that you define the inverse mapping without relying on template type deduction.
By far the easiest is to make
I
be defined outsideO
. Failing that, you need to define a way to findO<T>
givenO<T>::I
, like:we can then
and your code works.