为什么(隐式)实例化函数模板可以使用未声明的符号?

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

提问人:Jan Schultke 提问时间:7/14/2023 最后编辑:Jan Schultke 更新时间:7/16/2023 访问量:92

问:

我有以下代码:

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

请参阅编译器资源管理器

这里有一个我不明白的 clang 差异:

  • 无法调用的显式实例化,因为它尚未声明fun<A>(A)bar(A)
  • 同一位置的隐式实例化可以

GCC 和 MSVC 也使用显式实例进行编译,只有 clang 拒绝它。但是,我不相信标准允许编译任何一个版本:

对于函数模板专用化、成员函数模板专用化或类模板的成员函数或静态数据成员的专用化,如果专用化是隐式实例化的,因为它是从另一个模板专用化中引用的,并且引用它的上下文依赖于模板参数,则专用化的实例化点是封闭专用化的实例点。否则,此类专用化的实例化点将紧跟在引用专用化的命名空间范围声明或定义之后。

- [温度点]/1

fun<A>(A)是一个函数模板专用化,所以实例化点应该紧跟在 的定义之后。鉴于此,调用格式良好是没有意义的。call_funbar(A)

哪个编译器是正确的?它们都不合规吗?

C++ 模板 语言律师 显式实例化 C++26

评论

0赞 Nelfeal 7/14/2023
我也不相信,但它确实说命名空间范围......
0赞 Sam Varshavchik 7/14/2023
fun(A{});编译失败,如图所示。我不确定,但如果您声称它可以编译,那么显示的代码一定不是您声称编译的代码没有任何问题。
0赞 user12002570 7/14/2023
也许是IFNDR?请看这个答案
0赞 Jan Schultke 7/14/2023
@Nelfeal可能是我误解了标准在这里的含义。我将其解释为“遵循声明 AT 命名空间范围”,但预期的解释可能是“遵循命名空间范围的声明”。
0赞 Jan Schultke 7/14/2023
@Jason不,这不是IFNDR。如果模板是在 之后实例化的,则此代码的格式将正确。您提到的内容仅适用于模板的定义总是格式不正确的情况,而不管模板参数如何。bar

答:

3赞 Artyer 7/14/2023 #1

[温度点]p7

函数模板、成员函数模板或类模板的成员函数或静态数据成员的专用化可能在转换单元中具有多个实例化点,并且除了上述实例化点之外,

  • 对于在 Translation-Unit 的 Declaration-seq 中具有实例化点的任何此类专用化,在 private-module-fragment(如果有)之前,翻译单元的 declaration-seq 之后的点也被视为实例化点,并且
  • 对于在 private-module-fragment 中具有实例化点的任何此类专用化,翻译单元的末尾也被视为实例化点。

类模板的专用化在翻译单元中最多有一个实例化点。 任何模板的专用化都可能在多个翻译单元中具有实例化点。 如果两个不同的实例化点根据单一定义规则赋予模板专用化不同的含义,则程序格式不正确,无需诊断。

在翻译单元的末尾有第二个实例化点(您没有 private-module-fragment,第二个项目符号点不适用)。

如果在实例化的任一点实例化函数模板之间存在差异,则程序将是格式错误的 NDR,因此编译器可以自由选择任一点,而无需检查另一个点。

Clang 选择在实例化的最后一个点(翻译单元的末尾)实例化隐式实例化(引导它找到)。它还选择直接在实例化的第一个点实例化显式专用化(与 GCC 和 MSVC 不同),导致它找不到 .bar(A)bar(A)

您所拥有的是一个格式错误的 NDR 程序,因为在隐式或显式实例化之后直接使用实例化点和在翻译单元末尾使用实例化点具有不同的含义。

评论

0赞 Jan Schultke 7/14/2023
实例化的第一个点是在之后是正确的,还是我误读了那里的标准?[temp.point]/1 中的最后一句话非常容易解释,我读得越多。问题是程序是否只是格式良好,或者它是否是 IFNDR。call_foo
0赞 Artyer 7/15/2023
@JanSchultke我看不出它是如何解释的,你所说的似乎是正确的(第一个 POI 直接在定义之后)。因此,两个 POI(在 POI 后面找不到,可以在 TU 的末尾找到)具有“根据单一定义规则的不同含义”及其 IF-NDR}call_funvoid bar(A)call_fun