Reputation: 52739
I came across a strange situation today, where declaring a deleted operator with certain arguments changed the behaviour of seemingly unrelated code.
I reduced it to the following. Start with this:
namespace N
{
enum E { A, B };
struct C
{
C(E);
private:
C(int);
};
}
N::E operator|(N::E, N::E);
namespace N
{
void Waldo()
{
C(A | B);
}
}
Notice that C has two constructors, a public one and a private one. This code compiles, indicating that the public overload is being chosen, so the expression A | B
has type E
. In turn this means that the operator|(N::E, N::E)
has been matched (otherwise A
and B
would undergo implicit conversion to integers, the type of A | B
would be int
, and the private constructor would be matched.
So far so good. Now I define a new enumeration type F
, and a deleted operator|
that involves F:
namespace N
{
enum E { A, B };
struct C
{
C(E);
private:
C(int);
};
}
N::E operator|(N::E, N::E);
namespace N
{
enum F {};
int operator|(F, int) = delete;
void Waldo()
{
C(A | B);
}
}
Now the code doesn't compile, saying that C(int)
is private. This indicates that now A | B
has type int
, which means operator|(N::E, N::E)
is no longer being matched.
Why did the addition of the deleted operator|(F, int)
stop operator|(N::E, N::E)
from being matched?
Upvotes: 3
Views: 157
Reputation: 275385
The solution to your problem is simple.
Put your operator|
in the same namespace as the type. Now, ADL (argument dependent lookup) kicks in, and it is found even if there is the unrelated operator|
also visible.
Live example. Note that N::operator|
is found despite the |
being used in namespace Z
.
The proper place to overload free operators for a type is the namespace
that the type lives in, not the global namespace.
Upvotes: 1
Reputation: 477040
First off, note that being declared as delete
d is irrelevant, since deleted functions still take part in overload resolution.
Now, on to overload resolution. Cf. 13.3.1.2/3:
three sets of candidate functions, designated member candidates, nonmember candidates and built-in candidates, are constructed
(There are no member candidates, since E
is not a class type.) We know from that the operator overload is found by unqualified lookup. So when we consult 3.4.1 ("Unqualified lookup"), we find that
name lookup ends as soon as a declaration is found for the name.
Since you introduce the second operator overload within the namespace N
, it is found first, and name lookup stops. At this point, the overload set consists of your int N::operator|(N::F, int)
and the built-in operators. Continuing in 13.3.1.2/6:
The set of candidate functions for overload resolution is the union of the member candidates, the non-member candidates, and the built-in candidates.
Only the builtin is viable (since you cannot convert E
to F
implicitly), and thus it is chosen.
Upvotes: 5