namespace Test {
template<typename T>
void foo(T a) {
g(a);
}
struct L {
int s;
};
void bar(L a) {
g(a);
}
}
int main() {
foo(Test::L{1});
g(Test::L{5});
}
namespace Test {
void g(L a) {};
}
why in foo, g can be found, but in bar and main, g cannot be found. Tested with gcc 11.2.0 under C 11.Here is the error message.
/tmp/tmp.l8jCE9nUHA/test1.cpp: In function ‘void Test::bar(Test::L)’:
/tmp/tmp.l8jCE9nUHA/test1.cpp:33:9: error: ‘g’ was not declared in this scope
33 | g(a);
| ^
/tmp/tmp.l8jCE9nUHA/test1.cpp: In function ‘int main()’:
/tmp/tmp.l8jCE9nUHA/test1.cpp:39:5: error: ‘g’ was not declared in this scope
39 | g(Test::L{5});
| ^
CodePudding user response:
Even without the call to g
in main
and bar
, the program is still ill-formed, but with no diagnostic required.
The argument-dependent lookup in a template specialization is done from the point of instantiation of the specialization, not from the point of the definition of the template.
The specialization Test::foo<Test::L>
is implicitly instantiated due to the call foo(Test::L{1});
in main
. As a consequence it has two points of instantiation: One directly after the definition of main
and one at the end of the translation unit. (This is always the case for function template specializations.)
At the first point of instantiation Test::g
has not been declared yet and argument-dependent lookup would fail. At the second point of instantiation it would succeed since Test::g
has been declared there and can be found through argument-dependent lookup via the Test::L
argument.
If two points of instantiation give different meaning to the program, the program is IFNDR (ill-formed, no diagnostic required), meaning that it is still an invalid program, but the compiler doesn't have to tell you about it, analogues to ODR violations.
To avoid this you should declare all functions g
that Test::foo<Test::L>
would potentially use before the namespace scope declaration which is first making a call to Test::foo<Test::L>
(or making some other ODR-use of it). Here this means you should declare g
before main
's definition.
The calls g(Test::L{5});
in main
and g(a);
in bar
simply make the program ill-formed, with a diagnostic required, because neither appears in the definition of a templated entity, so argument-dependent lookup is done directly from where these calls appear and both of these are before g
has been declared, so that no g
can be found. To make them work g
must be declared before these calls.