HighCommander4
HighCommander4

Reputation: 52739

Unrelated deleted operator changes behaviour of overload resolution

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

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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

Kerrek SB
Kerrek SB

Reputation: 477040

First off, note that being declared as deleted 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

Related Questions