Search code examples
c++templateslanguage-lawyerexplicit-instantiationc++26

Why can (implicitly) instantiated function templates use undeclared symbols?


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);

See Compiler Explorer

There is a discrepancy for clang here that I don't understand:

  • an explicit instantiation of fun<A>(A) cannot call bar(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.

- [temp.point]/1

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?


Solution

  • [temp.point]p7:

    A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above,

    • for any such specialization that has a point of instantiation within the declaration-seq of the translation-unit, prior to the private-module-fragment (if any), the point after the declaration-seq of the translation-unit is also considered a point of instantiation, and
    • for any such specialization that has a point of instantiation within the private-module-fragment, the end of the translation unit is also considered a point of instantiation.

    A specialization for a class template has at most one point of instantiation within a translation unit. A specialization for any template may have points of instantiation in multiple translation units. If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.

    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 find bar(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.