For reasons I do not understand, the following C code fails to compile on VS 2022 (dialect set to C 20):
#include <compare>
namespace N1
{}
namespace N1::N2
{
class A {};
A operator-(A&);
}
std::strong_ordering operator-(std::strong_ordering o);
namespace N1
{
using namespace N2; // (1) !!!
std::strong_ordering foo();
inline std::strong_ordering bar()
{
return -foo(); // (2) !!!
}
}
At (2), the compiler files a complaint: error C2678: binary '-': no operator found which takes a left-hand operand of type 'std::strong_ordering' (or there is no acceptable conversion)
.
When the using namespace
directive at (1) is removed, the compiler happily finds the operator-
defined at global scope for the std::strong_ordering
type.
This gives rise to a set of questions:
- Is this VS 2022 behavior (a) a bug, (b) allowed or even (c) mandatory according to the language standard?
- In case of (b) or (c), how? Which specific sentences in the standard allow/mandate the compiler to not find the
operator-
at global scope? - How would you suggest to work around the issue, presuming that the
using namespace
directive is there to stay?
CodePudding user response:
Your compiler is correct, and I would expect other compilers to agree.
For the behaviour of a using-directive, see C 20 [namespace.udir]/2:
A using-directive specifies that the names in the nominated namespace can be used in the scope in which the using-directive appears after the using-directive. During unqualified name lookup (6.5.2), the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace.
In other words, if N1
contains using namespace N2;
, then only for the purposes of unqualified name lookup, names in N2
will appear as if they are in the lowest common ancestor namespace of N1
and N2
. Since N2
is inside N1
, the lowest common ancestor namespace is just N1
, and that means the operator-
in N2
appears inside N1
when unqualified name lookup is performed.
This means that unqualified lookup for operator-
will find N2::operator-
, and it won't proceed to the global namespace to continue searching for additional declarations. See [basic.lookup.unqual]/1:
In all the cases listed in 6.5.2, the scopes are searched for a declaration in the order listed in each of the respective categories; name lookup ends as soon as a declaration is found for the name. If no declaration is found, the program is ill-formed.
To work around the issue, there are two strategies. One is to place the operator-
for your type in the namespace where that type is declared, so it can be found via argument-dependent lookup. However, you are not allowed to add operator overloads to the std
namespace. The other strategy is to redeclare the operator-
you want using a using-declaration:
using namespace N2;
using ::operator-;
This effectively brings the operator-
that you want "one level deeper", putting it at the same level as the other operator-
that appears thanks to the using-directive, so unqualified name lookup will find both, and the compiler will perform overload resolution.
CodePudding user response:
I'm now working around the issue by having the operator-
live in std::
, i.e.:
...
namespace std
{
strong_ordering operator-(strong_ordering o);
}
...
However, I'm not really sure whether it is good practice to mess with the std::
namespace; comments welcome.