Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't understand name lookup differences between an int and a user defined type - perhaps ADL related

Why does the following code compile:

template<typename T>
void foo(T in) { bar(in); }

struct type{};
void bar(type) {}
int main() { foo(type()); }

When the following does not:

template<typename T>
void foo(T in) { bar(in); }

void bar(int) {}
int main() { foo(42); }

Compiling with GnuC++ 7:

a.cpp: In instantiation of 'void foo(T) [with T = int]':
a.cpp:9:20:   required from here
a.cpp:2:21: error: 'bar' was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
 void foo(T in) { bar(in); }
                  ~~~^~~~
a.cpp:8:6: note: 'void bar(int)' declared here, later in the translation unit void bar(int) {}

I would assume that MSVC would compile both (as it does) but that GCC would reject both since GCC/Clang have proper two phase name lookup...

like image 355
onqtam Avatar asked Sep 15 '17 14:09

onqtam


1 Answers

The strange part is not that the int example fails to compile, it is that the type example does since bar is defined after foo. This is due to [temp.dep.candidate] (see third paragraph).

Two-pass compilation of templates

When the compiler parses and compiles a template class or function, it looks up identifiers in two pass:

  • Template argument independent name lookup: everything that does not depend on the template arguments can be checked. Here, since bar() depends on a template argument, nothing is done. This lookup is done at the point of definition.
  • Template argument dependent name lookup: everything that could not be looked up in pass #1 is now possible. This lookup is done at the point of instantiation.

You get an error during pass #2.


ADL lookup

When a function name is looked up, it is done within the current context and those of the parameters type. For instance, the following code is valid though f is defined in namespace n:

namespace n { struct type {}; void f(type) {}; }
int main() { n::type t; f(t); } // f is found in ::n because type of t is in ::n

More about ADL (cppreference.com):

Argument-dependent lookup, also known as ADL, or Koenig lookup, is the set of rules for looking up the unqualified function names in function-call expressions, including implicit function calls to overloaded operators. These function names are looked up in the namespaces of their arguments in addition to the scopes and namespaces considered by the usual unqualified name lookup.


Two-pass compilation, ADL lookup and unqualified-id lookup

In your case, those three mechanisms collide. See [temp.dep.candidate]:

For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, 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 with external linkage from the template definition context are found.
— For the part of the lookup using associated namespaces (3.4.2), only function declarations with external linkage found in either the template definition context or the template instantiation context are found.

So, with foo(type()) unqualified-id lookup kicks in and the lookup is done "in either the template definition context or the template instantiation".
With foo(42), 42 being a fundamental type, ADL is not considered and only the "definition context" is considered.

like image 70
YSC Avatar answered Sep 19 '22 16:09

YSC