Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

error: no viable overloading with clang, compiles with gcc

The following program compiles fine with g++ (version 10.1.0) but not with clang++ (10.0.0)

#include <iostream>

template <typename U>
struct A { U x; };

namespace tools {
  template <typename U>
  void operator+=(A<U>& lhs, const A<U>& rhs) { lhs.x += rhs.x; }
}

namespace impl {
  template <typename U = int>
  void f() {
    A<U> a{3};
    A<U> b{2};
    a += b;
    std::cout << a.x << std::endl;
  }
}

namespace impl {
  using namespace tools;
}

int main()
{
  impl::f();
}

The error is:

name.cpp:16:7: error: no viable overloaded '+='
    a += b;
    ~ ^  ~
name.cpp:27:9: note: in instantiation of function template specialization 'impl::f<int>' requested here
  impl::f();

Clearly, moving the using namespace tools part before the template function impl::f() removes the error of clang.

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

What compiler is correct here? gcc or clang?

like image 792
francesco Avatar asked Jun 23 '20 08:06

francesco


2 Answers

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 +=.

like image 83
Oliv Avatar answered Nov 14 '22 20:11

Oliv


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 ]

like image 7
bartop Avatar answered Nov 14 '22 20:11

bartop