I have created a C
class inside a NS_C
namespace that way:
#include <iostream>
namespace NS_C {
template <typename T>
class C {
public:
C operator+(long) {
std::cout << "NS_C::C::operator+\n";
return *this;
}
void not_operator(C<T>, long) {
std::cout << "NS_C::C::not_operator\n";
}
void call() {
*this + 0;
not_operator(*this, 0);
}
};
}
The function call
is supposed to call NS_C::C::operator+
then NS_C::C::not_operator
. To test this behavior, I run this small program:
int main()
{
NS_C::C<int> ci;
ci.call();
return 0;
}
The output is what I expected:
> g++ -o example example.cpp && ./example
NS_C::C::operator+
NS_C::C::not_operator
Now, I want to create a new class A
within a separate namespace NS_A
and add to this namespace two generic overloads of operator+
and not_operator
functions:
#include <iostream>
namespace NS_A {
class A {};
template <typename T>
T operator+(T t, int)
{
std::cout << "NS_A::operator+\n";
return t;
}
template <typename T>
void not_operator(T, int)
{
std::cout << "NS_A::not_operator\n";
}
}
Thanks to ADL, a call to call
member function from an NS_C::C<NS_A>
object will call the overloaded NS_A::operator+
as it is better match (the second parameter is int
in NS_A::operator+
and long
in NS_C::C::operator+
).
However, I don't understand why the same behavior doesn't occur for my not_operator
function. Indeed, NS_C::C::not_operator
will still be called from call
function.
Let's use the following main function:
int main()
{
NS_C::C<NS_A::A> ca;
ca.call();
return 0;
}
I have the following output:
NS_A::operator+
NS_C::C::not_operator
Why NS_A::not_operator
is not called in that case?
Here is the complete code to reproduce the issue:
#include <iostream>
namespace NS_A {
class A {};
template <typename T>
T operator+(T t, int)
{
std::cout << "NS_A::operator+\n";
return t;
}
template <typename T>
void not_operator(T, int)
{
std::cout << "NS_A::not_operator\n";
}
}
namespace NS_C {
template <typename T>
class C {
public:
C operator+(long) {
std::cout << "NS_C::C::operator+\n";
return *this;
}
void not_operator(C<T>, long) {
std::cout << "NS_C::C::not_operator\n";
}
void call() {
*this + 0;
not_operator(*this, 0);
}
};
}
int main()
{
NS_C::C<int> ci;
ci.call();
NS_C::C<NS_A::A> ca;
ca.call();
return 0;
}
From overload_resolution#Call_to_an_overloaded_operator:
We have in overload sets of candidates for overloaded operator:
1) member candidates: if T1 is a complete class or a class currently being defined, the set of member candidates is the result of qualified name lookup of T1::operator@. In all other cases, the set of member candidates is empty.
2) non-member candidates: For the operators where operator overloading permits non-member forms, all declarations found by unqualified name lookup of operator@ in the context of the expression (which may involve ADL), except that member function declarations are ignored and do not prevent the lookup from continuing into the next enclosing scope. If both operands of a binary operator or the only operand of a unary operator has enumeration type, the only functions from the lookup set that become non-member candidates are the ones whose parameter has that enumeration type (or reference to that enumeration type)
Whereas for the other, we only have unqualified_lookup
There is even an example in unqualified_lookup#Overloaded_operator
showing difference between operator+(a, a)
and a + a