未实例化函数模板中的编译时错误
Compile-time error in uninstanciated function template
我对函数模板的理解一直是:如果包含invalid
C++ 并且您不实例化它们,您的项目将编译正常。
但是,下面的代码:
#include <cstdio>
#include <utility>
template <typename T>
void contains_compile_time_error(T&& t) {
int j = nullptr;
}
int main() {}
编译为:
x86-64 gcc 11.2
和标志 -std=c++20
;
x64 msvc v19.31
和标志 /std:c++20
;
并且不使用 x86-64 clang 14.0.0
(带有标志 -std=c++{11,14,17,20}
):
error: cannot initialize a variable of type 'int' with an rvalue of type 'std::nullptr_t'
int j = nullptr;
^ ~~~~~~~
1 error generated.
更让我困惑的是下面的代码:
#include <cstdio>
#include <utility>
namespace ns {
struct S {};
template <typename T>
void apply(T&& t) {
//print(std::forward<T>(t));
ns::print(std::forward<T>(t));
}
void print(S const&) { std::puts("S"); }
} // namespace ns
int main() {}
不编译:
x86-64 gcc 11.2
和标志 -std=c++20
;
x64 msvc v19.31
和标志 /std:c++20
;
x86-64 clang 14.0.0
和标志 -std=c++{11,14,17,20}
;
(他们都抱怨 print is not a member of 'ns'
)但确实用 x64 msvc v19.31 /std:c++17
.
编译
如果我调用不合格的 print
,那么代码将使用上述所有编译器进行编译。
所以,我的问题是:
- 我对函数模板的理解有误吗?
- 为什么上述编译器的行为与我发布的代码片段不同?
编辑 0: 根据 Frank 的评论,x64 msvc v19.31 /std:c++17 /permissive-
无法编译函数模板 apply
,我调用合格的 ns::print
。
My understanding of function templates has always been: if they contain invalid C++ and you don't instanciate them, your project will compile fine.
不,如果模板中的代码无效,那么程序也是如此。
但是“无效的 C++”是什么意思?您可以拥有语法上有效但语义上仍然无效的 C++,并且 C++ 的语义是高度上下文相关的。
因此,根据可用信息,可以在编译期间的不同时间检查多个级别的“有效”。至关重要的是,有些事情 理论上可以 尽早检查,但可能非常困难。因此,在验证模板定义的语义时,编译器有一些回旋余地。但是,无论代码是否有歧义,都是编译器检测损坏代码的能力。
why do the above compilers behave differently with the code snippets I posted?
就int j = nullptr
而言:
该程序在技术上是 ill-formed,但诊断是可选的。所以代码被破坏了,但是 GCC 并没有通过让它通过来破坏合规性。
例如,如果 S
只有私有构造函数,那么 print(std::forward<T>(t))
将被标记为已损坏,因为不可能 T
使其有效。但是,要求编译器足够聪明以在所有情况下都确定这一点有点刻薄。
对于print()
:
查找有点不同,有一些必须遵守的硬性规则。
这里涉及三种类型的查找。
- 符合条件的查找,例如
ns::print
- 不合格查找 不 涉及模板参数,如果您尝试
print(S{});
- 涉及模板参数的不合格查找,例如
print(std::forward<T>(t));
第三类的查找被推迟,但仍必须与其他两类的 non-template 函数一样执行。
请注意,即使在延迟查找的情况下,代码仍然需要在语法上有效,因此为什么在执行类型的依赖查找时需要添加 typename
。
至于 MSVC 允许 ns::print
:默认情况下,该编译器不符合标准的这个(和其他)方面。您需要使用 /permissive-
编译器标志来强制合规。
我对函数模板的理解一直是:如果包含invalid C++ 并且您不实例化它们,您的项目将编译正常。
但是,下面的代码:
#include <cstdio>
#include <utility>
template <typename T>
void contains_compile_time_error(T&& t) {
int j = nullptr;
}
int main() {}
编译为:
x86-64 gcc 11.2
和标志-std=c++20
;x64 msvc v19.31
和标志/std:c++20
;
并且不使用 x86-64 clang 14.0.0
(带有标志 -std=c++{11,14,17,20}
):
error: cannot initialize a variable of type 'int' with an rvalue of type 'std::nullptr_t'
int j = nullptr;
^ ~~~~~~~
1 error generated.
更让我困惑的是下面的代码:
#include <cstdio>
#include <utility>
namespace ns {
struct S {};
template <typename T>
void apply(T&& t) {
//print(std::forward<T>(t));
ns::print(std::forward<T>(t));
}
void print(S const&) { std::puts("S"); }
} // namespace ns
int main() {}
不编译:
x86-64 gcc 11.2
和标志-std=c++20
;x64 msvc v19.31
和标志/std:c++20
;x86-64 clang 14.0.0
和标志-std=c++{11,14,17,20}
;
(他们都抱怨 print is not a member of 'ns'
)但确实用 x64 msvc v19.31 /std:c++17
.
如果我调用不合格的 print
,那么代码将使用上述所有编译器进行编译。
所以,我的问题是:
- 我对函数模板的理解有误吗?
- 为什么上述编译器的行为与我发布的代码片段不同?
编辑 0: 根据 Frank 的评论,x64 msvc v19.31 /std:c++17 /permissive-
无法编译函数模板 apply
,我调用合格的 ns::print
。
My understanding of function templates has always been: if they contain invalid C++ and you don't instanciate them, your project will compile fine.
不,如果模板中的代码无效,那么程序也是如此。
但是“无效的 C++”是什么意思?您可以拥有语法上有效但语义上仍然无效的 C++,并且 C++ 的语义是高度上下文相关的。
因此,根据可用信息,可以在编译期间的不同时间检查多个级别的“有效”。至关重要的是,有些事情 理论上可以 尽早检查,但可能非常困难。因此,在验证模板定义的语义时,编译器有一些回旋余地。但是,无论代码是否有歧义,都是编译器检测损坏代码的能力。
why do the above compilers behave differently with the code snippets I posted?
就int j = nullptr
而言:
该程序在技术上是 ill-formed,但诊断是可选的。所以代码被破坏了,但是 GCC 并没有通过让它通过来破坏合规性。
例如,如果 S
只有私有构造函数,那么 print(std::forward<T>(t))
将被标记为已损坏,因为不可能 T
使其有效。但是,要求编译器足够聪明以在所有情况下都确定这一点有点刻薄。
对于print()
:
查找有点不同,有一些必须遵守的硬性规则。
这里涉及三种类型的查找。
- 符合条件的查找,例如
ns::print
- 不合格查找 不 涉及模板参数,如果您尝试
print(S{});
- 涉及模板参数的不合格查找,例如
print(std::forward<T>(t));
第三类的查找被推迟,但仍必须与其他两类的 non-template 函数一样执行。
请注意,即使在延迟查找的情况下,代码仍然需要在语法上有效,因此为什么在执行类型的依赖查找时需要添加 typename
。
至于 MSVC 允许 ns::print
:默认情况下,该编译器不符合标准的这个(和其他)方面。您需要使用 /permissive-
编译器标志来强制合规。