Unrelated deleted operator changes behaviour of overload resolution - c++

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?

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.

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.

Related

templated operator "+" overloading and enum initialization [constexpr][msvc]

Please, consider the following code:
template<typename T>
T operator+(const T& lvh, const T& rvh)
{
return lvh;
}
struct enum_container {
enum {
item1 = 1,
item2 = 2,
item3 = item1 + item2
};
};
int main() {
return 0;
}
Compiling with MS VS 2022 gives the following error:
error C2131: expression did not evaluate to a constant
message : failure was caused by call of undefined function or one not declared 'constexpr'
message : see usage of 'operator +'
The problem applies to the third element in enum.
Why cl tries to substitute template for the constant expression?
Compilation with gcc gives no errors.
I can eliminate the error with cl adding type specifier to the enum, like this:
enum : int {
...
}
But I can`t do this in the project I work with. Both enum and overload declared by 3rd party headers. All I can do is to change includ order. But I'm still wondering why it works that way...
To me it looks like MSVC is correct here.
First, because template argument deduction should happen before overload resolution to form a set of candidate functions:
Before overload resolution begins, the functions selected by name
lookup and template argument deduction are combined to form the set of
candidate functions
And then, since the non-member candidates (in this case template specialisation of the operator+ overload with the enum_container::<anonymous_enum> parameters) precede built-in candidates (conversion to the underlying built-in type, for which a user-defined overload would not be possible), the compiler is meant to resolve the overload to the instance of the template with the user-defined enum-type:
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)
built-in candidates: For operator,, the unary operator&, and the operator->, the set of built-in candidates is empty. For other
operators built-in candidates are the ones listed in built-in operator
pages as long as all operands can be implicitly converted to their
parameters. If any built-in candidate has the same parameter list as a
non-member candidate that isn't a function template specialization, it
is not added to the list of built-in candidates. When the built-in
assignment operators are considered, the conversions from their
left-hand arguments are restricted: user-defined conversions are not
considered.
Thus, the fact that GCC/Clang ignore the existence of the user-defined overload of operator+ under the enum definition doesn't seem to be compliant with the rules listed above. The same expression inside of a block scope causes exactly same error for all compilers (and I couldn't find any rationale why this might be different for the scope of enum definition):
struct enum_container {
enum {
item1 = 1,
item2 = 2,
item3 = item1 + item2
};
};
template<typename T>
T operator+(const T& lhs, const T&) {
return lhs;
}
int main() {
// error: call to non-'constexpr' function 'T operator+(const T&, const T&)
constexpr auto enumval = enum_container::item1 + enum_container::item2;
return 0;
}

Ambiguity in case of multiple inheritance and spaceship operator in C++20

In the following simplified program, struct C inherits from two structs A and B. The former defines both spaceship operator <=> and less operator, the latter – only spaceship operator. Then less operation is performed with objects of class C:
#include <compare>
struct A {
auto operator <=>(const A&) const = default;
bool operator <(const A&) const = default;
};
struct B {
auto operator <=>(const B&) const = default;
};
struct C : A, B {};
int main() {
C c;
c.operator<(c); //ok everywhere
return c < c; //error in GCC
}
The surprising moment here is that the explicit call c.operator<(c) succeeds in all compliers, but the similar call c<c is permitted by Clang but rejected in GCC:
error: request for member 'operator<=>' is ambiguous
<source>:8:10: note: candidates are: 'auto B::operator<=>(const B&) const'
<source>:4:10: note: 'constexpr auto A::operator<=>(const A&) const'
Demo: https://gcc.godbolt.org/z/xn7W9PaPc
There is a possibly related question: GCC can't differentiate between operator++() and operator++(int) . But in that question the explicit operator (++) call is rejected by all compilers, unlike this question where explicit operator call is accepted by all.
I thought that only one operator < is present in C, which was derived from A, and starship operator shall not be considered at all. Is it so and what compiler is right here?
gcc is correct here.
When you do:
c.operator<(c);
You are performing name lookup on something literally named operator<. There is only one such function (the one in A) so this succeeds.
But when you do c < c, you're not doing lookup for operator<. You're doing two things:
a specific lookup for c < c which finds operator< candidates (member, non-member, or builtin)
finding all rewritten candidates for c <=> c
Now, the first lookup succeeds and finds the same A::operator< as before. But the second lookup fails - because c <=> c is ambiguous (between the candidates in A and B). And the rule, from [class.member.lookup]/6 is:
The result of the search is the declaration set of S(N,T).
If it is an invalid set, the program is ill-formed.
We have an invalid set as the result of the search, so the program is ill-formed. It's not that we find nothing, it's that the whole lookup fails. Just because in this context we're looking up a rewritten candidate rather than a primary candidate doesn't matter, it's still a failed lookup.
And it's actually good that it fails because if we fix this ambiguous merge set issue in the usual way:
struct C : A, B {
+ using A::operator<=>;
+ using B::operator<=>;
};
Then our lookup would be ambiguous! Because now our lookup for the rewritten candidates finds two operator<=>s, so we end up with three candidates:
operator<(A const&, A const&)
operator<=>(A const&, A const&)
operator<=>(B const&, B const&)
1 is better than 2 (because a primary candidate is better than a rewritten candidate), but 1 vs 3 is ambiguous (neither is better than the other).
So the fact that the original fails, and this one also fails, is good: it's up to you as the class author to come up with the right thing to do - since it's not obvious what that is.
I reported the issue to Microsoft and they tell me that their and Clang behavior is right, while GCC is wrong: https://developercommunity.visualstudio.com/t/False-acceptance-of-ambiguity-in-case-of/1534112
Let me quote their answer here for completeness:
The compiler behavior you’re observing is by design as per the resolution outlined in https://cdacamar.github.io/wg21papers/proposed/spaceship-dr.html. The reason is that the compiler will find both A::operator<=>, A::operator<, and B::operator<=> as possible overload resolution candidates. Because A::operator< does not require rewriting the expression the compiler will not consider A::operator<=> because A::operator< is declared in the same scope with the same signature so the only remaining candidates are A::operator< and B::operator<=> but since <=> requires rewriting the expression it is dropped later and A::operator< is selected. You can observe that Clang has the same behavior here: https://godbolt.org/z/M6ffr95Ej

Using equality operators with boost::optional

I'm trying to define an equality operator for a type T defined in another namespace, and then use the equality operator on optional<T>. On clang (Apple LLVM 9.1.0), this code:
namespace nsp {
struct Foo {
};
}
bool operator==(const nsp::Foo& a, const nsp::Foo& b);
void foo() {
optional<nsp::Foo> a = none;
optional<nsp::Foo> b = none;
if (a == b)
;
}
Results in an error:
/usr/local/include/boost/optional/detail/optional_relops.hpp:29:34: error: invalid operands to binary expression ('const nsp::Foo' and 'const nsp::Foo')
{ return bool(x) && bool(y) ? *x == *y : bool(x) == bool(y); }
~~ ^ ~~
MWE.cpp:40:19: note: in instantiation of function template specialization 'boost::operator==<what3words::engine::nsp::Foo>' requested here
if (a == b)
^
/usr/local/include/boost/optional/detail/optional_relops.hpp:28:6: note: candidate template ignored: could not match 'optional<type-parameter-0-0>' against 'const nsp::Foo'
bool operator == ( optional<T> const& x, optional<T> const& y )
What's happening? My guess is that it's something to do with the Koenig lookup rules...
Immediate fix
Do this:
namespace nsp {
bool operator==(const Foo& a, const Foo& b);
}
to fix your problem.
If you have control over Foo, you can instead do:
namespace nsp {
struct Foo {
friend bool operator==(const Foo& a, const Foo& b) {
return true;
}
};
}
which is optimal if Foo is a template class.
What went wrong with your solution
What is going on here is that optional is in std (or boost or whatever) and in that namespace it tries to do a nsp::Foo == nsp::Foo call.
There is a == that doesn't apply in the ::std namespace, so it won't look in ::; once it finds any == it stops looking, even if the arguments are completely unrelated. It also looks for == in the namespaces associated with the arguments -- in this case ::nsp. But it never looks in :: here either.
When adding operators to a type, always define the operator in the namespace of the type.
Namespaces can be reopened. So if you don't have control over the header file, you can create a new header file with the == in it. This == has to be visible at every point where optional<nsp::Foo>::operator== is invoked or your program is ill formed due to ODR violations (and, in this case, also generates a compiler error, which is useful to avoid heizenbugs).
The long version
When you invoke an operator (or a function), lookup follows a few simple steps.
First it looks around locally (in the local namespace). If it finds anything there, this search stops. (This includes using ns::identifier; names injected into the namespace, but usually not using namespace foo;). This "stop" occurs even if the function or operator won't work for the types in question; any == for any types whatsoever in a namespace stops this search.
If that fails to find a match, it starts looking in enclosing namespaces until it finds the function/operator, or reaches the root namespace. If there are using namespace foo; declarations, the functions/operators in those namespaces are considered to be in the "common parent" namespace of both the using namespace location and the namespace being imported. (So using namespace std; in namespace foo makes it seem like std is in ::, not in foo).
The result generates a collection of candidates for overload resolution.
Next, ADL (argument dependent lookup) is done. The associated namespaces of all function/operator arguments are examined. In addition, the associated namespaces of all the type arguments of the templates are also examined (recursively).
Operators/functions that match the name are collected. For ADL, parent namespaces are not examined.
These two collections of operators/functions are your candidates for overload resolution.
In your case, the namespace where == is called is boost. boost has plenty of == operators (even if they don't apply), so all of the == in boost are candidates.
Next, we examine the namespace of the arguments -- nsp::Foo in this case. We look in nsp and see no ==.
Then we run overload resolution on those. No candidates work, and you get a compiler error.
Now, when you move your user-defined == into namespace nsp, it is then added to the set of == found in the ADL step. And as it matches, it is called.
Overload resolution (what it does with the candidates) is its own complex subject. The short form is that it tries to find the overload that involves the least amount of conversion; and if two cases match exactly as well as each other, it prefers non-template over template and non-variardic over variardic.
There is a lot of detail in "least amount of conversion" and "exactly" that can mislead programmers. The most common is is that converting a Foo lvalue to Foo const& is a small amount of conversion, convering it to template<class T> T&& or T& is no conversion.
Indeed, you should enable ADL by declaring the operator== implementation inside an associated namespace for Foo. This fixes that:
#include <boost/optional.hpp>
namespace nsp {
struct Foo { };
bool operator==(const Foo&, const Foo&) { return false; }
}
int main() {
boost::optional<nsp::Foo> a;
boost::optional<nsp::Foo> b;
return (a == b)? 0 : 1;
}

typecast operator in private base

I found something I consider strange behaviour in C++: a typecast operator in a private base class is confusing the compiler when trying to resolve an implicit cast:
#include <iostream>
struct Base
{
#ifdef ENABLE
operator bool () const { return true; }
#endif
};
struct Derived : private Base
{
operator int () const { return 7; }
};
int main()
{
Derived o;
std::cout << o << '\n';
return 0;
}
Without -DENABLE, the code compiles just fine, and outputs 7. With -DENABLE, the code no longer compiles, complaining about an ambiguous overload. I tried gcc-4.6.5, gcc-4.8.1, and clang-3.3. What's confusing is that I clearly cannot ask for (bool)o, because Base is a private base.
Is this expected behaviour?
Access control always comes last. Quote from the Standard:
10.2 Member name lookup [class.member.lookup]
1 Member name lookup determines the meaning of a name (id-expression)
in a class scope (3.3.7). Name lookup can result in an ambiguity, in
which case the program is ill-formed. For an id-expression, name
lookup begins in the class scope of this; for a qualified-id, name
lookup begins in the scope of the nestedname- specifier. Name lookup
takes place before access control (3.4, Clause 11).
8 If the name of an overloaded function is unambiguously found,
overloading resolution (13.3) also takes place before access control.
Ambiguities can often be resolved by qualifying a name with its class
name.
The reason both operators are considered is that a) the base clas conversion is not hidden by the derived one (which it would if both had been converting to the same type), b) both bool and int can be written to stdout, c) neither is a better match than the other so overload resolution yields an ambiguity. This yields a hard error even before access control comes into play.

How does `is_base_of` work?

How does the following code work?
typedef char (&yes)[1];
typedef char (&no)[2];
template <typename B, typename D>
struct Host
{
operator B*() const;
operator D*();
};
template <typename B, typename D>
struct is_base_of
{
template <typename T>
static yes check(D*, T);
static no check(B*, int);
static const bool value = sizeof(check(Host<B,D>(), int())) == sizeof(yes);
};
//Test sample
class Base {};
class Derived : private Base {};
//Expression is true.
int test[is_base_of<Base,Derived>::value && !is_base_of<Derived,Base>::value];
Note that B is private base. How does this work?
Note that operator B*() is const. Why is it important?
Why is template<typename T> static yes check(D*, T); better than static yes check(B*, int); ?
Note: It is reduced version (macros are removed) of boost::is_base_of. And this works on wide range of compilers.
If they are related
Let's for a moment assume that B is actually a base of D. Then for the call to check, both versions are viable because Host can be converted to D* and B*. It's a user defined conversion sequence as described by 13.3.3.1.2 from Host<B, D> to D* and B* respectively. For finding conversion functions that can convert the class, the following candidate functions are synthesized for the first check function according to 13.3.1.5/1
D* (Host<B, D>&)
The first conversion function isn't a candidate, because B* can't be converted to D*.
For the second function, the following candidates exist:
B* (Host<B, D> const&)
D* (Host<B, D>&)
Those are the two conversion function candidates that take the host object. The first takes it by const reference, and the second doesn't. Thus the second is a better match for the non-const *this object (the implied object argument) by 13.3.3.2/3b1sb4 and is used to convert to B* for the second check function.
If you would remove the const, we would have the following candidates
B* (Host<B, D>&)
D* (Host<B, D>&)
This would mean that we can't select by constness anymore. In an ordinary overload resolution scenario, the call would now be ambiguous because normally the return type won't participate in overload resolution. For conversion functions, however, there is a backdoor. If two conversion functions are equally good, then the return type of them decides who is best according to 13.3.3/1. Thus, if you would remove the const, then the first would be taken, because B* converts better to B* than D* to B*.
Now what user defined conversion sequence is better? The one for the second or the first check function? The rule is that user defined conversion sequences can only be compared if they use the same conversion function or constructor according to 13.3.3.2/3b2. This is exactly the case here: Both use the second conversion function. Notice that thus the const is important because it forces the compiler to take the second conversion function.
Since we can compare them - which one is better? The rule is that the better conversion from the return type of the conversion function to the destination type wins (again by 13.3.3.2/3b2). In this case, D* converts better to D* than to B*. Thus the first function is selected and we recognize the inheritance!
Notice that since we never needed to actually convert to a base class, we can thereby recognize private inheritance because whether we can convert from a D* to a B* isn't dependent on the form of inheritance according to 4.10/3
If they are not related
Now let's assume they are not related by inheritance. Thus for the first function we have the following candidates
D* (Host<B, D>&)
And for the second we now have another set
B* (Host<B, D> const&)
Since we cannot convert D* to B* if we haven't got a inheritance relationship, we now have no common conversion function among the two user defined conversion sequences! Thus, we would be ambiguous if not for the fact that the first function is a template. Templates are second choice when there is a non-template function that is equally good according to 13.3.3/1. Thus, we select the non-template function (second one) and we recognize that there is no inheritance between B and D!
Let's work out how it works by looking at the steps.
Start with the sizeof(check(Host<B,D>(), int())) part. The compiler can quickly see that this check(...) is a function call expression, so it needs to do overload resolution on check. There are two candidate overloads available, template <typename T> yes check(D*, T); and no check(B*, int);. If the first is chosen, you get sizeof(yes), else sizeof(no)
Next, let's look at the overload resolution. The first overload is a template instantiation check<int> (D*, T=int) and the second candidate is check(B*, int). The actual arguments provided are Host<B,D> and int(). The second parameter clearly doesn't distinguish them; it merely served to make the first overload a template one. We'll see later why the template part is relevant.
Now look at the conversion sequences that are needed. For the first overload, we have Host<B,D>::operator D* - one user-defined conversion. For the second, the overload is trickier. We need a B*, but there are possibly two conversion sequences. One is via Host<B,D>::operator B*() const. If (and only if) B and D are related by inheritance will the conversion sequence Host<B,D>::operator D*() + D*->B* exist. Now assume D indeed inherits from B. The two conversion sequences are Host<B,D> -> Host<B,D> const -> operator B* const -> B* and Host<B,D> -> operator D* -> D* -> B*.
So, for related B and D, no check(<Host<B,D>(), int()) would ambiguous. As a result, the templated yes check<int>(D*, int) is chosen. However, if D does not inherit from B, then no check(<Host<B,D>(), int()) is not ambiguous. At this point, overload resolution cannot happen based on shortest conversion sequence. However, given equal conversion sequences, overload resolution prefers non-template functions, i.e. no check(B*, int).
You now see why it doesn't matter that the inheritance is private: that relation only serves to eliminate no check(Host<B,D>(), int()) from overload resolution before the access check happens. And you also see why the operator B* const must be const: else there's no need for the Host<B,D> -> Host<B,D> const step, no ambiguity, and no check(B*, int) would always be chosen.
The private bit is completely ignored by is_base_of because overload resolution occurs before accessibility checks.
You can verify this simply:
class Foo
{
public:
void bar(int);
private:
void bar(double);
};
int main(int argc, char* argv[])
{
Foo foo;
double d = 0.3;
foo.bar(d); // Compiler error, cannot access private member function
}
The same applies here, the fact that B is a private base does not prevent the check from taking place, it would only prevent the conversion, but we never ask for the actual conversion ;)
It possibly has something to do with partial ordering w.r.t. overload resolution. D* is more specialized than B* in case D derives from B.
The exact details are rather complicated. You have to figure out the precedences of various overload resolution rules. Partial ordering is one. Lengths/kinds of conversion sequences is another one. Finally, if two viable functions are deemed equally good, non-templates are chosen over function templates.
I've never needed to look up how these rules interact. But it seems partial ordering is dominating the other overload resolution rules. When D doesn't derive from B the partial ordering rules don't apply and the non-template is more attractive. When D derives from B, partial ordering kicks in and makes the function template more attractive -- as it seems.
As for inheritance being privete: the code never asks for a conversion from D* to B* which would require public inheritence.
Following on your second question, note that if it weren't for const, Host would be ill-formed if instantiated with B == D. But is_base_of is designed such that each class is a base of itself, hence one of conversion operators must be const.