Search code examples
c++templateslanguage-lawyerinstantiationname-lookup

Point of instantiation of default arguments in a template function


The standard allows function templates to be instantiated after the enclosing namespace-scope declaration or at the end of the translation unit when they are referred to from a non-template context: [temp.point]/1

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]/8

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 translation unit, 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.

Now consider this minimal reproducible example:

#include <iostream>
#include <array>
struct A {};
std::array<char, 2> show(float, A)
{
    std::cout << "2\n";
    return {};
}
template<typename T>
struct Fun {
    decltype(show(0, T{})) b;
};
template <typename T>
void func(T, int c = sizeof(Fun<T>{}.b))
{
    show(0, T{});
    std::cout << c << '\n';
}
int main()
{
    func(A{});
}
char show(int, A)
{
    std::cout << "1\n";
    return {};
}

Both GCC and Clang output 1 2 (godbolt).

Here, the instantiation of func<A> (triggered in main) has two points of instantiation: one immediately after main (and thus before the second show) and another at the end of the translation unit. The first 1 indicates that the compilers to instantiate func<A> at the end of the translation unit. However, the default argument sizeof(Fun<T>{}.b) causes Fun<A> to be instantiated, and the second 2 suggests that Fun<A> is instantiated before the second show.

Now, the point of instantiation of default arguments is specified to be that of func<A>: [temp.point]/2

If a function template or member function of a class template is called in a way which uses the definition of a default argument of that function template or member function, the point of instantiation of the default argument is the point of instantiation of the function template or member function specialization.

Hmm ... This seems to suggest that the two numbers should be the same.

I feel I'm missing something here. Is there any detail that I happened to neglect? Or did I make mistakes?


Solution

  • As quoted in the question [temp.point]/8 says:

    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.

    According to the one-definition rule two definitions are not the same if function call overload resolution of a name used in the definition will yield different entities defined outside the definition. ([basic.def.odr]/6.2)

    Overload resolution of the two calls to show in func<A> and in Fun<A> will choose different function overloads depending on whether func<A>'s point of instantiation is immediately after main or at the end of the translation unit, both of which are allowed points of instantiation.

    Therefore the program is ill-formed, no diagnostic required.