Home > database >  C `using namespace` directive makes global-scope operator disappear?
C `using namespace` directive makes global-scope operator disappear?

Time:04-02

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:

  1. Is this VS 2022 behavior (a) a bug, (b) allowed or even (c) mandatory according to the language standard?
  2. 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?
  3. How would you suggest to work around the issue, presuming that the using namespace directive is there to stay?

Live demo

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.

  • Related