模板函数中默认参数的实例化点

Point of instantiation of default arguments in a template function

该标准允许函数模板在封闭的命名空间范围声明之后或在从非模板上下文引用它们时在翻译单元的末尾实例化:[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.

现在考虑这个最小的可重现示例:

#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 {};
}

GCC 和 Clang 都输出 1 2 (godbolt).

这里,func<A>的实例化(在main中触发)有两个实例化点:一个紧接在main之后(因此在第二个show之前)另一个在翻译单元的末尾。第一个 1 表示编译器在翻译单元的末尾实例化 func<A> 。但是,默认参数 sizeof(Fun<T>{}.b) 导致实例化 Fun<A>,第二个 2 表明 Fun<A> 在第二个 show 之前实例化。

现在,默认参数的实例化点指定为 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.

嗯...这似乎表明这两个数字应该相同。

我觉得我在这里遗漏了一些东西。有没有我碰巧忽略的细节?还是我做错了?

正如问题 [temp.point]/8 中引用的那样:

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.

根据单一定义规则,如果定义中使用的名称的函数调用重载解析将产生定义外定义的不同实体,则两个定义不相同。 ([basic.def.odr]/6.2)

func<A>Fun<A>中对show的两次调用的重载解析将根据func<A>的实例化点是否立即选择不同的函数重载在 main 之后或翻译单元的末尾,这两个都是允许的实例化点。

因此程序格式错误,不需要诊断