gcc 接受而 clang 拒绝带有嵌套通用 lambda 的此代码,为什么?
gcc accepts and clang rejects this code with nested generic lambdas, why?
根据 Godbolt 的编译器资源管理器(参见 demo),以下代码使用 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;
}
此外,如果我将 auto n
替换为 int n
或者如果我将 if-else 替换为三元运算符 (return n < 2 ? 1 : (n * h(h)(n - 1));
).[=16,即使 GCC 和 MSVC 也会拒绝代码=]
这个程序格式错误,不需要诊断,所以两个实现(实际上,任何实现)都是正确的。违反的规则是 [temp.res.general]/6.4:
a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter
这里的模板是最里面的 operator()
:它使用了立即包含 operator()
的特化(它是其成员的闭包类型作为模板参数),它具有推导的 return 类型。这里的“紧随其后”(仍然)在 return 语句中,应该从中推导出该类型,因此由于 h(h)
不涉及模板参数,实例化将是格式错误的那个模板的(即,n
的类型)。
GCC 和 MSVC 直到最终实例化(int
为 10)才对推导的 return 类型进行语义检查,此时 return类型是已知的:它只是最内层 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);}
根据 Godbolt 的编译器资源管理器(参见 demo),以下代码使用 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;
}
此外,如果我将 auto n
替换为 int n
或者如果我将 if-else 替换为三元运算符 (return n < 2 ? 1 : (n * h(h)(n - 1));
).[=16,即使 GCC 和 MSVC 也会拒绝代码=]
这个程序格式错误,不需要诊断,所以两个实现(实际上,任何实现)都是正确的。违反的规则是 [temp.res.general]/6.4:
a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter
这里的模板是最里面的 operator()
:它使用了立即包含 operator()
的特化(它是其成员的闭包类型作为模板参数),它具有推导的 return 类型。这里的“紧随其后”(仍然)在 return 语句中,应该从中推导出该类型,因此由于 h(h)
不涉及模板参数,实例化将是格式错误的那个模板的(即,n
的类型)。
GCC 和 MSVC 直到最终实例化(int
为 10)才对推导的 return 类型进行语义检查,此时 return类型是已知的:它只是最内层 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);}