提问人:Jan Schultke 提问时间:7/14/2023 最后编辑:Jan Schultke 更新时间:7/16/2023 访问量:92
为什么(隐式)实例化函数模板可以使用未声明的符号?
Why can (implicitly) instantiated function templates use undeclared symbols?
问:
我有以下代码:
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_fun
bar(A)
哪个编译器是正确的?它们都不合规吗?
答:
函数模板、成员函数模板或类模板的成员函数或静态数据成员的专用化可能在转换单元中具有多个实例化点,并且除了上述实例化点之外,
- 对于在 Translation-Unit 的 Declaration-seq 中具有实例化点的任何此类专用化,在 private-module-fragment(如果有)之前,翻译单元的 declaration-seq 之后的点也被视为实例化点,并且
- 对于在 private-module-fragment 中具有实例化点的任何此类专用化,翻译单元的末尾也被视为实例化点。
类模板的专用化在翻译单元中最多有一个实例化点。 任何模板的专用化都可能在多个翻译单元中具有实例化点。 如果两个不同的实例化点根据单一定义规则赋予模板专用化不同的含义,则程序格式不正确,无需诊断。
在翻译单元的末尾有第二个实例化点(您没有 private-module-fragment,第二个项目符号点不适用)。
如果在实例化的任一点实例化函数模板之间存在差异,则程序将是格式错误的 NDR,因此编译器可以自由选择任一点,而无需检查另一个点。
Clang 选择在实例化的最后一个点(翻译单元的末尾)实例化隐式实例化(引导它找到)。它还选择直接在实例化的第一个点实例化显式专用化(与 GCC 和 MSVC 不同),导致它找不到 .bar(A)
bar(A)
您所拥有的是一个格式错误的 NDR 程序,因为在隐式或显式实例化之后直接使用实例化点和在翻译单元末尾使用实例化点具有不同的含义。
评论
call_foo
}
call_fun
void bar(A)
call_fun
评论
fun(A{});
编译失败,如图所示。我不确定,但如果您声称它可以编译,那么显示的代码一定不是您声称编译的代码没有任何问题。bar