Search code examples
c++gccoperator-overloadinglanguage-lawyerargument-dependent-lookup

GCC and ADL for operators in expressions


Consider this code sample

template <typename T> struct S { T t; };

template <class T> void foo(const S<T> &v)
{
  bar(v.t);
}

namespace N
{
  struct A {};
} 

void bar(const N::A &a) {}

int main()
{
  S<N::A> a;
  foo(a);    
}

The code fails to compile in GCC and Clang, since neither regular lookup nor ADL can resolve the call to bar from foo. This is perfectly expected, since the list of associated namespaces for bar call is just N. Global namespace is not included, global bar is not found. All as it should be.

However, if I change it to

template <typename T> struct S { T t; };

template <class T> void foo(const S<T> &v)
{
  +v.t;
}

namespace N
{
  struct A {};
} 

void operator +(const N::A& a) {}

int main()
{
  S<N::A> a;
  foo(a);    
}

It suddenly begins to compile successfully in GCC. (Meanwhile, Clang rejects both versions of the code).

It appears that in the second (operator-based) version of the code GCC considers global namespace as an associated namespace for ADL as well.

If in the latter version of the code I change the call to

template <class T> void foo(const S<T> &v)
{
  operator +(v.t);
}

It will again fail to compile in GCC. So, it appears some sort of special treatment is given to operators-in-expressions notation specifically, but not to function-call notation.

It this behavior standard? I don't seem to find it in the text of the document (searching for "associated namespace"), although I do vaguely remember reading something about this peculiarity of GCC.


Solution

  • This is gcc bug 51577. The second test case there is pretty much exactly your code example.

    There is no special rule for operator lookup that would look in the global namespace. [over.match.oper]/3 has:

    The set of non-member candidates is the result of the unqualified lookup of operator@ in the context of the expression according to the usual rules for name lookup in unqualified function calls ([basic.lookup.argdep]) except that all member functions are ignored.

    The usual rules for name lookup in unqualified function calls does not include the global namespace: [basic.lookup.argdep]/2:

    If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of its associated classes.

    N::A is a class type, its associated class is itself, its associated namespaces are the innermost enclosing namespaces, which is just N, not ::.