以下代码在 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};
}
这似乎是一种很奇怪的行为。这是怎么回事?
我已经对 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 是错误的。