error: no viable overloading with clang, compiles with gcc

Clang is right: unqualified dependent name lookup considers only declarations that are visible at the point of definition of the template

In your example, the operator+= is a dependent name in the function template f, in which case unqualified name lookup for the a += b; call considers only declarations that are visible at the point of definition of the function template f. As the tools namespace is added as a nominated namespace to impl only after the point of definition of f, unqual. name lookup will not see the declarations brought in from tools, and will fail to tools::operator+=. Thus, Clang is right here, whereas GCC as well as MSVC, is wrong in not rejecting the code.

This behaviour for GCC seems to only be present when the dependent name refers to an operator function, whereas if we replace the operator with a named function, GCC also rejects the code.

Rejected by Clang, accepted by GCC:

struct Dummy{};

namespace ns_g {
    template <typename T>
    bool operator!(T) { return true; } 
}  // namespace ns_f

namespace ns_f {
    template <typename T>
    void f() { 
        (void)(!T{});
    }
    
    // Add ns_g as a nominated namespace to ns_f
    // _after_ point of definition of ns_f::f.
    using namespace ns_g;
}  // namespace ns_f

int main() {
    ns_f::f<Dummy>();
    return 0;
}

Rejected by both Clang and by GCC:

struct Dummy{};

namespace ns_g {
    template <typename T>
    bool g(T) { return true; } 
}  // namespace ns_f

namespace ns_f {
    template <typename T>
    void f() { 
        (void)(g(T{}));
    }
    
    // Add ns_g as a nominated namespace to ns_f
    // _after_ point of definition of ns_f::f.
    using namespace ns_g;
}  // namespace ns_f

int main() {
    ns_f::f<Dummy>();
    return 0;
}

where, for the latter, GCC even gives us a note that:

note: 'template<class T> bool ns_g::g(T)' declared here, later in the translation unit.

This inconsistency alone hints that GCC is wrong in the former example, and we may not that Clang's language compatibility page explicitly mentions that some versions of GCC may accept invalid code:

Language Compatibility

[...]

Unqualified lookup in templates

Some versions of GCC accept the following invalid code: [...]

Even if the particular example pointed out by Clang is rejected also by more recent GCC versions, the context of this questions is the same.


Open bug report on GCC

Note that the OP (and answerer) to a similar SO question (which I found long after all the answers landed on this question), to which this question is probably a duplicate:

  • Dependent name lookup in function template: clang rejects, gcc accepts

submitted a bug report on GCC that is yet to be claimed/addressed:

  • Bug 70099 - Function found by ADL, but shouldn't be visible at point of definition

(All ISO Standard references below refer to N4659: March 2017 post-Kona working draft/C++17 DIS)

Standard references

Even if [temp.res]/9 states [extract, emphasis mine]:

[temp.res]/9 When looking for the declaration of a name used in a template definition, the usual lookup rules ([basic.lookup.unqual], [basic.lookup.argdep]) are used for non-dependent names. The lookup of names dependent on the template parameters is postponed until the actual template argument is known ([temp.dep]). [ Example: ... ] [...]

[temp.dep.res]/1 is clear that only declarations that are visible at the point of definition of the template are considered for non-qualified (dependent) name lookup [emphasis mine]:

[temp.dep.res]/1 In resolving dependent names, names from the following sources are considered:

  • (1.1) Declarations that are visible at the point of definition of the template.
  • (1.2) Declarations from namespaces associated with the types of the function arguments both from the instantiation context ([temp.point]) and from the definition context.

a fact that is repeated in [temp.dep.candidate]/1 [emphasis mine]:

[temp.dep.candidate]/1 For a function call where the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules ([basic.lookup.unqual], [basic.lookup.argdep]) except that:

  • (1.1) For the part of the lookup using unqualified name lookup, only function declarations from the template definition context are found.
  • (1.2) For the part of the lookup using associated namespaces ([basic.lookup.argdep]), only function declarations found in either the template definition context or the template instantiation context are found.

where the wording template definition context is used instead of point of definition of the template, but afaik these are equivalent.

As per [namespace.udir]/2 [emphasis mine]:

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, the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace. [ Note: In this context, “contains” means “contains directly or indirectly”.  — end note ]

placing the using directive after the point of definition of the function template f is equivalent to simply declaring a name after the same point of definition, and as expected, the following modified example is rejected by Clang but accepted by GCC:

struct Dummy{};

namespace ns_f {
    template <typename T>
    void f() { 
        (void)(!T{});
    }

    template <typename T>
    bool operator!(T) { return true; } 
}  // namespace ns_f

int main() {
    ns_f::f<Dummy>();
    return 0;
} 

Finally, note that ADL, (1.2) in [temp.dep.candidate]/1 above, does not apply here, as ADL do not proceed to enclosing scopes.


Non-dependent constructs may be diagnosed without instantiations

Additional note An important point here is that f is a template function. Without template parameters the code would not compile neither with gcc, nor with clang.

If A were to be made into a non-template class, say

struct A { int x; };

then [temp.res]/8.3 applies, and the program is ill-formed, no diagnostic required:

[temp.res]/8 Knowing which names are type names allows the syntax of every template to be checked. The program is ill-formed, no diagnostic required, if:

[...]

(8.3) a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or

[...]


The code is ill-formed because the part of unqualified name look-up that is not argument dependent is performed in the template definition context. So Clang is right and the GCC bug is already reported (bug #70099)

What followes is the long explanation.

Inside your exemple code there are some place that must be marked, to allow the discussion:

namespace impl {
  template <typename U = int>
  void f() {                       // (1) point of definition of the template f
    A<U> a{3};
    A<U> b{2};
    a += b;                        //  call operator += with arguments of dependent type A<U> 
    std::cout << a.x << std::endl;
  }
}

namespace impl {
  using namespace tools;          // using directive     
}

int main()
{
  impl::f();
}                                 // (2) point of instantiation of impl::f<int>

At the definition of the template f (1), the call to the operator += is performed with arguments of type A<U>. A<U> is a dependent type, so operator += is a dependent name.

[temp.dep.res]/1 describe how operator += is looked up:

For a function call where the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules from the template definition context ([basic.lookup.unqual], [basic.lookup.argdep]). [ Note: For the part of the lookup using associated namespaces ([basic.lookup.argdep]), function declarations found in the template instantiation context are found by this lookup, as described in [basic.lookup.argdep]. — end note ][...]

There are two look-ups that are performed.

Non argument dependent unqualified name look up [basic.lookup.unqual].

This look-up is performed from the template definition context. "from the template definition context" means the context at the point of definition of the template. The term "context" refers to the look-up context. If the template f was first declared in namespace impl and then defined in the global namespace scope, unqualified name look-up would still find members of namespace impl. This is why the rule [temp.dep.res]/1 use "the template definition context" and not simply "template definition point".

This look-up is performed from (1) and it does not find the operator += defined in namespace tools. The using directive is appears later than (1), and has no effect.

Argument dependent name look-up (ADL) [basic.lookup.argdep]

ADL is performed at the point of instantiation (2). So it is realized after the using directive. Nevertheless, ADL only considers namespace associated to the type of the arguments. The arguments have type A<int>, the template A is a member of the global namespace, so only members of this namespace can be find by ADL.

At (2) there are no operator += declared in the global namespace scope. So ADL also fails to find a declaration for operator +=.


Seems clang is right here according to this. In short - you are extending your namespace but using namespace should 'propagate' to this extension only forward.

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 ([basic.lookup.unqual]), the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace. [ Note: In this context, “contains” means “contains directly or indirectly”. — end note ]