12

Short question: do operators have special template lookup rules for overload resolution with internal linkage or is the code at the bottom a template overload resolution bug for operators in GCC?

The detail: instead of pasting a chunk of code I'll take you through my reasoning. Let's start with some simple code:

#include <iostream>

template<typename T> struct A{ T b; };
struct B{};

template<typename T> 
void foo (const A<T>&a) { foo(a.b); } 
void foo (const B&) { std::cout << "hello"; }

int main(){
  A<B> b;
  foo(b);
}

The above prints "hello", everything is fine.

Now let's put both foo in an anonymous namespace:

namespace {
template<typename T> 
void foo (const A<T>&a) { foo(a.b); } 
void foo (const B&) { std::cout << "hello"; }
}

The code now fails to compile. Clang says error: call to function 'foo' that is neither visible in the template definition nor found by argument-dependent lookup and GCC template argument deduction/substitution failed.

This is broken because foo(const B&) is defined after foo<T> and doesn't have an external linkage, as explained in n4296:

[basic.link] An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage.

[temp.point] The instantiation context of an expression that depends on the template arguments is the set of declarations with external linkage declared prior to the point of instantiation of the template specialization in the same translation unit.

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

  • For the part of the lookup using unqualified name lookup (3.4.1), only function declarations from the template definition context are found.

  • For the part of the lookup using associated namespaces (3.4.2), only function declarations found in either the template definition context or the template instantiation context are found.

Now the same thing using operators:

struct ostream {} cout; 
template<typename T> struct A{ T t; };
struct B{};

namespace {
template<typename T>
ostream& operator<< (ostream& out, const A<T>&v) 
  { return out << v.t; }
ostream& operator<< (ostream& out, const B&) 
  { return out; }
}

int main(){
  A<B> a;
  cout << a; 
}

GCC (4.7/4.8/4.9) is now perfectly happy with the code and gives zero warning with -Wall -Wextra -pedantic -ansi while clang complains about 'operator<<' the same way it did for foo.

I didn't find any exception for operator overload lookup in the standard so I believe this is a bug (feature?) in GCC but template resolution rules are not easy so I thought I might check here before filing a bug.

You can see this code live here.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
Thibaut
  • 2,400
  • 1
  • 16
  • 28
  • Possibly related to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51577 – T.C. Mar 06 '15 at 17:30
  • Thanks for pointing that out @T.C. There is indeed something screwy about operator lookup in GCC. I will still file it as a separate bug since the context seems different enough but the root cause might be the same. – Thibaut Mar 06 '15 at 18:00
  • I got the `foo` code to compile on GCC by switching the order of the definitions. Does that match what you'd expect? Here's [my fork of your live example](http://ideone.com/HBFsO4). – Austin Mullins Mar 06 '15 at 20:10
  • 1
    @AustinMullins : Yes, it does. The point here is that the example with `foo` is ill-formatted according to the standard, and it should be as well for the `operator<<`, but it isn't (for gcc). Your fix for the code is correct, and clang even tells you what to do if you try to compile the example above: `'foo' should be declared prior to the call site or in the global namespace` – Thibaut Mar 06 '15 at 20:18

1 Answers1

2

This is definitely a bug in gcc. The code below rightly prints right with clang but wrong with GCC.

#include <iostream>

template<typename T> struct A{ T t; };
struct B{};
struct C : public B{};

std::ostream& operator<< (std::ostream& out, const B&) 
  { return out << "right"; }

namespace {
template<typename T>
std::ostream& operator<< (std::ostream& out, const A<T>&v) 
  { return out << v.t; }

std::ostream& operator<< (std::ostream& out, const C&) 
  { return out << "wrong"; }
}

int main(){
  A<C> a;
  std::cout << a;
}

Reported here.

Thibaut
  • 2,400
  • 1
  • 16
  • 28