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

2

9

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

Subserve answered 15/9, 2017 at 14:38 Comment(6)
I don't know why it doesn't work, but in the second example, moving bar() to the top of the file makes the example work.Lookeron
@ArnavBorborah exactly - but Gcc/clang both accept the first piece of code - which I thought they shouldn'tSubserve
@Subserve Could you expand on why you think they shouldn't accept it? Phase-two lookups include the set of ADL-found functions that are declared between the template and its point of instantiation.Cornet
@Cornet perhaps I need to comprehend what ADL is all about... but why would there be a difference between an int and a user defined type?Subserve
@Subserve because int is a fundamental type (second list, first point).Cornet
When you ask why something does not compile, always copy the error message to your question.Gelasius
D
4

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.

Dyanne answered 15/9, 2017 at 15:9 Comment(2)
Note: MSVC does not respect C++ regaring the two-pass compilation. I guess it considers the two samples valid.Dyanne
while we are on the MSVC topic - read >>this<< :)Subserve
I
4

The 1st sample is valid, because ADL takes effect for the name lookup of dependent name in template definition; which makes it possible to find the function bar. (bar(in) depends on the template parameter T.)

(emphasis mine)

For a dependent name used in a template definition, the lookup is postponed until the template arguments are known, at which time ADL examines function declarations that are visible from the template definition context as well as in the template instantiation context, while non-ADL lookup only examines function declarations that are visible from the template definition context (in other words, adding a new function declaration after template definition does not make it visible except via ADL).

And ADL doesn't work with fundamental types, that's why the 2nd sample fails.

Increase answered 15/9, 2017 at 15:3 Comment(0)
D
4

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.

Dyanne answered 15/9, 2017 at 15:9 Comment(2)
Note: MSVC does not respect C++ regaring the two-pass compilation. I guess it considers the two samples valid.Dyanne
while we are on the MSVC topic - read >>this<< :)Subserve

© 2022 - 2024 — McMap. All rights reserved.