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);}