gcc接受并拒绝带有嵌套泛型lambda的此代码,为什么?
根据 Godbolt 的 Compiler Explorer(参见演示),以下代码使用 GCC(10.2 和主干)编译并输出 3628800,但无法使用 Clang(11.0.1 和主干)编译。在这两种情况下,-std=c++17都使用。它还可以使用 MSVC 19 进行编译,但不能使用其他编译器进行编译。为什么会这样,谁的行为是正确的?
#include <iostream>
int main()
{
std::cout << [](auto f) { return f(f); }(
[](auto& h) {
return [&h](auto n) {
if (n < 2)
return 1;
else
return n * h(h)(n - 1);
};
})(10) << std::endl;
}
此外,即使GCC和MSVC拒绝的代码,如果我更换auto n用int n或如果我三元运算符替换的if-else( return n < 2 ? 1 : (n * h(h)(n - 1));)。
回答
该程序格式错误,不需要诊断,因此两个实现(实际上,任何实现)都是正确的。违反的规则是 [temp.res.general]/6.4:
由于不依赖于模板参数的构造,紧跟其定义的模板的假设实例化将是格式错误的
这里的模板是最里面的operator():它使用了直接包含operator()(它是其成员的闭包类型作为模板参数)的特化,它具有推导的返回类型。“紧接着”这里是(仍然)从该类型将被推断非常return语句内,所以实例化将导致形成不良的,只是h(h),它不涉及一个模板参数是模板(即,类型n)。
GCC 和 MSVC 在最终实例化之前不会对推导的返回类型进行语义检查(int对于 10),此时返回类型是已知的:它只是最内层 lambda 的适当变体。但是,如果最里面的 lambda 不是通用的,则在包含 的实例化期间进行检查operator(),并且会发生相同的错误。
这种行为差异可以在一个更简单的例子中看到:
auto g();
template<class T> auto f(T x) {return g()-x;}
Clang 在这一点上已经拒绝了,但是 GCC 接受了,也许紧随其后
auto g() {return 1;}
int main() {return f(1);}