简体   繁体   中英

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 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 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?

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM