具有递归不完整类型和泛型 lambda 参数的编译器行为

问题描述 投票:0回答:1

以下代码在 GCC 14.1 (

-std=c++20
) (godbolt) 下可以成功编译,但在 Clang 18.1.0 (godbolt) 上由于某种原因无法编译:

template <class F>
struct Fix {
    F f;
    using Ptr = decltype(&F::template operator()<Fix<F>>); // member function pointer "void ((lambda)::*)(Fix<(lambda)>) const"
};

int main() {
    auto lambda = []([[maybe_unused]] auto self) -> void {};
    [[maybe_unused]] auto hi = Fix{lambda};
}
fix.cpp:8:44: error: variable has incomplete type 'Fix<(lambda at fix.cpp:8:19)>'
    8 |     auto lambda = []([[maybe_unused]] auto self) -> void {};
      |                                            ^
fix.cpp:4:39: note: in instantiation of function template specialization 'main()::(anonymous class)::operator()<Fix<(lambda at fix.cpp:8:19)>>' requested here
    4 |     using Ptr = decltype(&F::template operator()<Fix<F>>); // member function pointer "void ((lambda)::*)(Fix<(lambda)>) const"
      |                                       ^
fix.cpp:9:32: note: in instantiation of template class 'Fix<(lambda at fix.cpp:8:19)>' requested here
    9 |     [[maybe_unused]] auto hi = Fix{lambda};
      |                                ^
fix.cpp:2:8: note: definition of 'Fix<(lambda at fix.cpp:8:19)>' is not complete until the closing '}'
    2 | struct Fix {
      |        ^
1 error generated.

这是一个演示定点组合器概念的程序。参数

auto self
旨在接收
Fix<(lambda)>
。需要类型变量
Ptr
来向类添加更复杂或实用的功能。

Fix
的定义中,需要实例化
Fix<F>
本身,这可能会导致编译器错误“variable has an incomplete type”。

有人可以告诉我发生这种情况的原因吗?这是规范问题还是错误?

更奇怪的是,以下代码确实可以使用 Clang 进行编译:

template <class F>
struct Fix {
    F f;
    using Ptr = decltype(&F::template operator()<Fix<F>>); // member function pointer "void ((lambda)::*)(Fix<(lambda)>) const"
};

int main() {
    auto lambda = []([[maybe_unused]] auto self) -> void {};

    // Added
    using F = decltype(lambda);
    using Ptr = decltype(&F::template operator()<Fix<F>>);

    [[maybe_unused]] auto hi = Fix{lambda};
}

这似乎是一种很奇怪的行为。这是怎么回事?

c++ lambda c++20 clang++ incomplete-type
1个回答
0
投票

我已经对 lambda 进行了脱糖处理并删除了不必要的细节。剩下的就是这个了:

template <class F>
struct Fix {
    using Ptr = decltype(&F::template operator()<Fix<F>>);
};

struct Lambda {
    constexpr void operator()(auto) const {}
};

int main() {
    Fix<Lambda>();
}

与原始示例一样,GCC 接受它,而 Clang 拒绝它。通过这种脱糖形式,我们可以观察到一个可能相关的事实:如果删除

constexpr
,Clang 就会开始接受代码。

这个事实表明,Clang 拒绝代码与函数需要持续评估有关。在 [temp.inst]/8 下,当需要一个函数进行常量求值时,它的定义会被隐式实例化。在 [expr.const]/21.6 下,常量求值需要

Lambda::operator()
的特化,因为它是一个 constexpr 函数,并由表达式
&F::template operator()<Fix<F>>
命名,该表达式可能是在 [expr.const]/ 下求值的常量21.4 因为它是出现在模板化实体(类模板
Fix
)内的地址表达式。在实例化
Lambda::operator()
的定义时,它会受到 [dcl.fct.def.general]/2 的约束,它禁止不完整类型作为函数定义的参数类型或返回类型。

然而,直到我们到达

main
时,这个实例化才真正发生,此时模板
Fix
的定义已经被看到了。代码格式不正确的论点一定是当
Fix<Lambda>
实例化时
Lambda::operator()<Fix<Lambda>>
仍然不完整,因为前者仍在实例化过程中(即使 template 已经完全定义)。该标准似乎为
Fix<Lambda>
提供了与
Lambda::operator()<Fix<Lambda>>
相同的实例化点,因此它似乎没有对这个问题给出明确的答案。在我看来,Clang 的行为看起来就像我所期望的那样,基于这样的想法:编译器位于通过从主模板逐一实例化成员声明来生成具体实例化的“中间”,因此还没有完成
Fix<Lambda>
。我不确定 GCC 如何接受它,但由于这里的标准并不明确,所以我不能肯定地说 GCC 是错误的。
    

© www.soinside.com 2019 - 2024. All rights reserved.