如何理解#1664 提议的决议
How to understand the proposed resolution of #1664
看了#1664(proposed resolution 1664)的提议决议后,我对函数模板的默认参数的规则感到困惑,引用这里的内容:
根据 8.1.5 [expr.prim.lambda] 第 3 段
The closure type is declared in the smallest block scope, class scope, or namespace scope that contains
the corresponding lambda-expression. [Note: This determines the set of namespaces and classes associated with the closure type (6.4.2 [basic.lookup.argdep]). The parameter types of a lambda-
declarator do not affect these associated namespaces and classes. —end note]
但是,17.8.1 [temp.inst] 第 13 段说
If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point.
那么,一种可能性是模板函数(或者,可能是 class 模板的成员函数)默认参数中 lambda 表达式的闭包类型被认为是在虚构函数模板特化的主体中的某个块范围内声明。
考虑以下示例:
namespace J {
inline namespace K {
template <typename T> int zap(const T &t) { foo(t); return 0; }
template <typename T> void zip(int = zap([] { })) { }
}
template <typename T> void foo(const T &) { }
}
void bar() {
J::K::zip<long>();
/*Accroding to the above wording,the invoke just like:
=> J::K::zip<long>(zap([] { }));
*/
}
If zip were not a template, argument-dependent lookup successfully resolves the lookup for foo in all implementations tested; however, there is implementation variance in the handling of the example as written.
提议的决议(2013 年 9 月):
Change 17.8.1 [temp.inst] paragraph 13 as follows:
If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point, except that the scope in which a closure type is declared (8.1.5 [expr.prim.lambda]) — and therefore its associated namespaces — remain as determined from the context of the definition for the default argument. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f.
注意强调的部分,如果我没理解错的话,意思是如果强调的部分注释掉了,foo
就无法通过argument dependent look up 因为参数 [] { }
which namespace 既不是 J
也不是 K
,假设 function bar
中的形式类似于 J::K::zip<long>(zap([] { }) /*default argument*/);
,所以根据 [ expr.prim.lambda] paragraph 3 [] { }
的命名空间在fuction bar
,在那个范围内,找不到foo
,所以强调的部分是为了这种情况下 zap
中的 [] { }
的命名空间与 zap
相同,这意味着 [] { }
的命名空间是 K
,现在 foo
可以通过参数依赖查找规则在父命名空间 J
中找到,到目前为止,如果我误解了这些规则,请更正 me.the 其他观点是每次计算默认参数时被调用的函数,即使默认是 non-dependent。所以继续考虑下面的代码:
#include <iostream>
struct A {
};
template<typename T>
int func(T, float) { //#a
std::cout << "float" << std::endl;
return 0;
}
template<typename T>
void test(int = func(A{}, 0)) { //#1
}
template<typename T>
int func(T, int) { //#b
std::cout << "int" << std::endl;
return 0;
}
int main() {
test<A>(); //#2 transform to: test<A>(func(A{}, 0)); here,#b should be the best match
std::getchar();
}
虽然默认参数func
是非依赖的,但是每次调用函数test
时应该确定它,我在一些编译器中测试了代码。
MSVC所有版本报告"int",gcc报告"float",clang报告"float",这是什么鬼?根据gcc或clang的报告,好像是func
在 #1
确定,MSVC 证明 func
在 #2
确定。如果 MSVC 是错误的,那意味着非依赖默认参数可以在#1 内确定,不需要每次调用函数时都确定,为什么强调部分需要添加?(如果我理解强调部分正确,其目的是使默认参数中闭包类型的命名空间保持一致,无论 lambda 表达式是在函数声明点还是调用点 )。如果我误解了这些规则,如何正确解读它们?
更新:
gcc 9.1以上版本无法编译#1664中提到的代码,会报错(the complie result)
问题:
1.Does函数模板或非模板函数的非依赖默认参数需要在每次调用相应函数时确定?
2.what是"the definition for the default argument"的意思吗?这个写法严格吗?(换句话说,我的理解是,添加的规则实际上想表达的是closeure 类型是一个函数声明,其中包含一个包含相应 lambda 表达式的默认参数,对吗?如果我对此的理解有误,请纠正我)
默认参数每次被调用时都会被计算,但这是一个运行时属性:调用不是按源代码行计算,而是按实际控制流计算。另外,模板函数的默认参数是 considered to be a definition 并且在需要时被 实例化 ,每个 specialization 最多一次函数(关于必须同意的多个实例化点的通常附带条件)。 CWG1664 是一个非常狭窄的问题,基于 措辞 的实例化:通过引入 fictitious 函数模板,它留下了 lambda 声明的可能性“身体上”移动。该修复确实只影响 ADL。
您的 func
示例反而说明了模板中通常的 name-lookup 规则:无论多少次和 whence test
的默认值参数被实例化,其中 func
不是 dependent 名称,因此找到 func(T,float)
(每次)。众所周知,MSVC 从未正确实施此规则(因为,公平地说,他们的实施早于该规则,而且他们最近才开始对其模板支持进行必要的(并且几乎完成)重写) .
与此同时,最近的 GCC 在 CWG1664 示例中显然存在错误:请注意,它抱怨 foo
被使用但未定义, 矛盾 两个明显可见的 { }
及其之前有关未找到它的错误消息。
看了#1664(proposed resolution 1664)的提议决议后,我对函数模板的默认参数的规则感到困惑,引用这里的内容:
根据 8.1.5 [expr.prim.lambda] 第 3 段
The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [Note: This determines the set of namespaces and classes associated with the closure type (6.4.2 [basic.lookup.argdep]). The parameter types of a lambda- declarator do not affect these associated namespaces and classes. —end note]
但是,17.8.1 [temp.inst] 第 13 段说
If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point.
那么,一种可能性是模板函数(或者,可能是 class 模板的成员函数)默认参数中 lambda 表达式的闭包类型被认为是在虚构函数模板特化的主体中的某个块范围内声明。
考虑以下示例:
namespace J {
inline namespace K {
template <typename T> int zap(const T &t) { foo(t); return 0; }
template <typename T> void zip(int = zap([] { })) { }
}
template <typename T> void foo(const T &) { }
}
void bar() {
J::K::zip<long>();
/*Accroding to the above wording,the invoke just like:
=> J::K::zip<long>(zap([] { }));
*/
}
If zip were not a template, argument-dependent lookup successfully resolves the lookup for foo in all implementations tested; however, there is implementation variance in the handling of the example as written.
提议的决议(2013 年 9 月):
Change 17.8.1 [temp.inst] paragraph 13 as follows:
If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point, except that the scope in which a closure type is declared (8.1.5 [expr.prim.lambda]) — and therefore its associated namespaces — remain as determined from the context of the definition for the default argument. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f.
注意强调的部分,如果我没理解错的话,意思是如果强调的部分注释掉了,foo
就无法通过argument dependent look up 因为参数 [] { }
which namespace 既不是 J
也不是 K
,假设 function bar
中的形式类似于 J::K::zip<long>(zap([] { }) /*default argument*/);
,所以根据 [ expr.prim.lambda] paragraph 3 [] { }
的命名空间在fuction bar
,在那个范围内,找不到foo
,所以强调的部分是为了这种情况下 zap
中的 [] { }
的命名空间与 zap
相同,这意味着 [] { }
的命名空间是 K
,现在 foo
可以通过参数依赖查找规则在父命名空间 J
中找到,到目前为止,如果我误解了这些规则,请更正 me.the 其他观点是每次计算默认参数时被调用的函数,即使默认是 non-dependent。所以继续考虑下面的代码:
#include <iostream>
struct A {
};
template<typename T>
int func(T, float) { //#a
std::cout << "float" << std::endl;
return 0;
}
template<typename T>
void test(int = func(A{}, 0)) { //#1
}
template<typename T>
int func(T, int) { //#b
std::cout << "int" << std::endl;
return 0;
}
int main() {
test<A>(); //#2 transform to: test<A>(func(A{}, 0)); here,#b should be the best match
std::getchar();
}
虽然默认参数func
是非依赖的,但是每次调用函数test
时应该确定它,我在一些编译器中测试了代码。
MSVC所有版本报告"int",gcc报告"float",clang报告"float",这是什么鬼?根据gcc或clang的报告,好像是func
在 #1
确定,MSVC 证明 func
在 #2
确定。如果 MSVC 是错误的,那意味着非依赖默认参数可以在#1 内确定,不需要每次调用函数时都确定,为什么强调部分需要添加?(如果我理解强调部分正确,其目的是使默认参数中闭包类型的命名空间保持一致,无论 lambda 表达式是在函数声明点还是调用点 )。如果我误解了这些规则,如何正确解读它们?
更新:
gcc 9.1以上版本无法编译#1664中提到的代码,会报错(the complie result)
问题:
1.Does函数模板或非模板函数的非依赖默认参数需要在每次调用相应函数时确定?
2.what是"the definition for the default argument"的意思吗?这个写法严格吗?(换句话说,我的理解是,添加的规则实际上想表达的是closeure 类型是一个函数声明,其中包含一个包含相应 lambda 表达式的默认参数,对吗?如果我对此的理解有误,请纠正我)
默认参数每次被调用时都会被计算,但这是一个运行时属性:调用不是按源代码行计算,而是按实际控制流计算。另外,模板函数的默认参数是 considered to be a definition 并且在需要时被 实例化 ,每个 specialization 最多一次函数(关于必须同意的多个实例化点的通常附带条件)。 CWG1664 是一个非常狭窄的问题,基于 措辞 的实例化:通过引入 fictitious 函数模板,它留下了 lambda 声明的可能性“身体上”移动。该修复确实只影响 ADL。
您的 func
示例反而说明了模板中通常的 name-lookup 规则:无论多少次和 whence test
的默认值参数被实例化,其中 func
不是 dependent 名称,因此找到 func(T,float)
(每次)。众所周知,MSVC 从未正确实施此规则(因为,公平地说,他们的实施早于该规则,而且他们最近才开始对其模板支持进行必要的(并且几乎完成)重写) .
与此同时,最近的 GCC 在 CWG1664 示例中显然存在错误:请注意,它抱怨 foo
被使用但未定义, 矛盾 两个明显可见的 { }
及其之前有关未找到它的错误消息。