【发布时间】:2020-12-10 10:04:52
【问题描述】:
#include <iostream>
namespace J {
template <typename T> void zip(int = zap([] { })) { } //#1
template <typename T> int zap(const T &t) {return 0; }
}
int main(){
J::zip<long>();
}
考虑上面的代码,这是proposed resolution 1664 的简化示例。注意标有#1 的地方,我怀疑为什么zap 的名称可以在实例化的上下文中查找。我认为zap不是依赖名,依赖名的定义如下:
temp.dep
在一个表达式中:
后缀表达式(表达式列表opt)
如果后缀表达式是非限定 ID,则非限定 ID 表示从属名称,如果
- 表达式列表中的任何表达式都是包扩展,
- 表达式列表中的任何表达式或花括号初始化列表都依赖于类型,或者
- unqualified-id 是一个模板 ID,其中任何模板参数都依赖于模板参数。
我认为zap([] { }) 不满足上述任何条件,因为表达式[] { } 的类型不是依赖类型。虽然下面的规则说与closure-type关联的命名空间是这样确定的:
temp.inst#11
如果以需要使用默认参数的方式调用函数模板 f,则查找依赖名称,检查语义约束,并且在默认参数中使用的任何模板的实例化都像默认参数是函数模板特化中使用的初始值设定项,具有与当时使用的函数模板 f 相同的范围、相同的模板参数和相同的访问权限,除了声明闭包类型的范围( [expr.prim.lambda.closure]) - 以及它相关的命名空间 - 仍然由默认参数定义的上下文确定。这种分析称为默认参数实例化。然后将实例化的默认参数用作 f 的参数。
但是,这些来自模板定义上下文和实例化上下文的名称仅被考虑用于从属名称,由:
temp.dep.res
在解析从属名称时,会考虑来自以下来源的名称:
- 在模板定义处可见的声明。
- 来自与函数参数类型关联的命名空间的声明,来自实例化上下文 ([temp.point]) 和定义上下文。
模板定义中使用的非依赖名称使用通常的名称查找并在它们被使用的地方绑定。
所以,我认为,为了遵守上述这些规则,zap 的名称查找只发生在使用它的点(即#1),因为它不是从属名称,即,则根本不考虑实例化(ADL)上下文中的名称。
我在三个实现中测试了代码,outcomes 列在下面:
-
Clang9.0 and higher version 将
zap视为从属名称。 - version under 8.0 of Clang 报告了一个没有意义的错误。
-
version under
9.1of gcc 将zap视为从属名称。 -
version higher than
9.1of gcc 将zap视为非依赖名称,并且不会在实例化上下文中执行zap的名称查找。
那么,zap 的名称查找究竟经历了什么过程?似乎最新版本的 GCC 同意将 zap 视为非依赖名称,这导致它无法找到 zap 的名称。如果我错过了标准中的其他规则,我会感谢您指出。
【问题讨论】:
-
好吧,所有“三个”编译器似乎都同意
[] { }指的是zip的每个特化的不同类,所以也许它应该被认为依赖于T。不知道在哪里/如果那在标准中。 godbolt.org/z/M1d875 -
@HTNW 因为
[]{}的类型不同,请注意as if the default argument had been an initializer used in a function template specialization。所以对于每个特化trigger<int>,trigger<char>,表达式[]{}的闭包类型是不同的,因为它们在它们的函数模板特化中分别是不同的本地类类型。但是 [temp.dep.type] 中没有任何规则说函数模板的本地类类型应该被视为依赖类型。 -
zip 和 zap 函数定义的交换行解决了所有编译错误...
-
@Cloud 关键是默认参数中的lambda表达式的闭包类型是否可以被认为是依赖类型。
标签: c++ templates c++17 language-lawyer