jac*_*k X 8 c++ templates language-lawyer c++17
看了#1664的提议分辨率(提议分辨率1664),我对函数模板的默认参数的规则感到困惑,在这里引用内容:
闭包类型在包含相应 lambda 表达式的最小块作用域、类作用域或命名空间作用域中声明。[注意:这决定了与闭包类型(6.4.2 [basic.lookup.argdep])相关联的命名空间和类的集合。lambda 声明符的参数类型不会影响这些关联的命名空间和类。——尾注]
如果以需要使用默认参数的方式调用函数模板 f,则查找依赖名称,检查语义约束,并且默认参数中使用的任何模板的实例化都像默认参数一样完成是在函数模板特化中使用的初始化器,与当时使用的函数模板 f 具有相同的作用域、相同的模板参数和相同的访问权限。
那么,一种可能性是,模板函数(或者,可能是类模板的成员函数)的默认参数中 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([] { }));
*/
}
Run Code Online (Sandbox Code Playgroud)
如果 zip 不是模板,则依赖参数的查找成功地解决了所有测试实现中对 foo 的查找;但是,在处理编写的示例时存在实现差异。
将 17.8.1 [temp.inst] 第 13 段更改如下:
如果以需要使用默认参数的方式调用函数模板 f,则查找依赖名称,检查语义约束,并实例化在默认参数中使用的任何模板都被完成,就好像默认参数是在函数模板特化中使用的初始化程序,具有与当时使用的函数模板 f 相同的范围、相同的模板参数和相同的访问权限,除了声明闭包类型的范围(8.1.5 [expr.prim.lambda]) - 因此它的关联命名空间 - 仍然由默认参数的定义的上下文确定. 这种分析称为默认参数实例化。实例化的默认参数然后用作 f 的参数。
注意强调的部分,如果我没有误解,这意味着如果强调的部分注释掉,则foo无法通过参数依赖查找来查找,因为[] { }名称空间既不是也不J是K,假设形式为function barlike J::K::zip<long>(zap([] { }) /*default argument*/);,所以根据[ expr.prim.lambda]第3款的命名空间[] { }是fuction bar在那个范围,没有foo可以发现,所以强调的部分是这个情况考虑的命名空间[] { }中zap的一样zap,它意味着的命名空间[] { }是K,现在foo可以在父命名空间中找到J通过参数依赖查找规则,到目前为止,如果我误解了这些规则,请纠正我。另一种观点是每次调用函数时都会评估默认参数,即使默认值是非依赖的。所以继续考虑以下代码:
#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();
}
Run Code Online (Sandbox Code Playgroud)
虽然默认参数func是非依赖的,但是每次test调用函数时都应该确定它,我在一些编译器中测试代码。
MSVC 的所有版本都报告“int”,gcc 报告“float”,clang 报告“float”,这是什么鬼?根据gcc 或clang 的报告,似乎func是确定在#1,MSVC 证明func是确定在#2. 如果MSVC错误,那意味着可以在#1内确定非依赖的默认参数,不需要每次调用函数时都确定,为什么强调部分需要添加?(如果我正确理解强调的部分,它的目的是使默认参数中的闭包类型的命名空间保持一致,无论 lambda 表达式是在函数声明点还是调用点)。如果我误解了这些规则,如何正确解释它们?
gcc 9.1以上版本无法编译#1664中提到的代码,会报错(编译结果)
1.函数模板或非模板函数的非依赖默认参数是不是每次调用对应的函数时都需要确定?
2.“默认参数的定义”是什么意思?这个措辞严格吗?(换句话说,我的理解是,添加的规则想要表达的是closeure类型的命名空间是一个函数声明的命名空间,其中包含一个默认参数,其中包含相应的 lambda 表达式,对吗?如果我对此的理解是错误的,请纠正我)
默认参数在每次调用时都会进行计算,但这是一个运行时属性:调用不是按源代码行计数,而是按实际控制流计数。另外,模板化函数的默认参数被视为定义,并在需要时实例化,每次函数专门化最多实例化一次(通常的附带条件是多个实例化点必须一致)。CWG1664 是一个非常狭窄的问题,基于实例化的措辞方式:通过引入虚构的函数模板,它留下了 lambda\xe2\x80\x99s 声明 \xe2\x80\x9cphysically\xe2\x80\x9d 移动的可能性。该修复确实只影响 ADL。
\n\n您的func示例反而说明了模板中常见的名称查找规则:无论 \xe2\x80\x99s 默认参数实例化多少次以及何时 test实例化,func它都不是依赖名称,因此func(T,float)每次都会找到。 众所周知, MSVC从未正确实现此规则(因为,公平地说,他们的实现早于该规则,并且他们\xe2\x80\x99 最近才开始对其模板支持进行必要的(并且几乎完全)重写)。
与此同时,最近的 GCC 在 CWG1664 示例中明显存在错误:请注意,它抱怨foo已被使用但未定义,这与明显可见的内容及其之前关于未找到它的错误消息相矛盾。{ }