I have the following code:
template <typename T>
void fun(T t) {
// foo and bar are not declared yet, but this is okay,
// because they can be found through ADL for a class type T
foo(t);
bar(t);
}
struct A {};
void foo(A);
// implicitly instantiate fun<A>(A), with the point of instantiation being after call_fun
void call_fun() {
fun(A{});
}
/* implicit instantiation should be here:
template void fun<A>(A t) {
foo(t); // OK, foo has been declared
bar(t); // NOT OK, bar has not been declared yet
}
*/
// uncommenting the following explicit instantiation makes the code ill-formed
// template void fun(A);
void bar(A);
There is a discrepancy for clang here that I don't understand:
- an explicit instantiation of
fun<A>(A)
cannot callbar(A)
because it has not been declared yet - an implicit instantiation at the same location can
GCC and MSVC compile with the explicit instantiation too, only clang rejects it. However, I'm not convinced that compiling either version is allowed by the standard:
For a function template specialization, a member function template specialization, or a specialization for a member function or static data member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization and the context from which it is referenced depends on a template parameter, the point of instantiation of the specialization is the point of instantiation of the enclosing specialization. Otherwise, the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that refers to the specialization.
fun<A>(A)
is a function template specialization, so the point of instantiation should immediately follow the definition of call_fun
. Given this, it makes no sense that the call to bar(A)
is well-formed.
Which compiler is right? Are all of them non-compliant?
[temp.point]p7:
There is a second point of instantiation at the end of the translation unit (you have no private-module-fragment, the second bullet point doesn't apply).
If there was a difference between instantiating the function template at either point of instantiation, the program would be ill-formed NDR, so the compiler is free to choose either point without checking the other one.
Clang chooses to instantiate implicit instantiations at the last point of instantiation, the end of the translation unit (leading it to find
bar(A)
). It also chooses to instantiate explicit specializations directly at the first point of instantiation (unlike GCC and MSVC), leading it to not findbar(A)
.What you have is an ill-formed NDR program, since using the point of instantiation directly after an implicit or explicit instantiation and the point of instantiation at the end of the translation unit have different meanings.