Clang主干版本为何拒绝这段带折叠展开约束的C++代码?
咱们先把你给出的代码补全成可复现的完整版本,方便分析问题:
#include <type_traits> template <class T> concept floating_point = std::is_floating_point_v<T>; template <class T> concept integral = std::is_integral_v<T>; template <class T> concept arithmetic = floating_point<T> || integral<T>; // ------------------------------------ template <arithmetic... Ts> constexpr int f() { return 1; } template <floating_point... Ts> constexpr int f() { return 2; } int main() { static_assert(f<float>() == 2); return 0; }
你大概率会觉得,既然floating_point是arithmetic的严格子集(毕竟arithmetic就是floating_point或integral的合集),那第二个f()重载应该比第一个更具约束性,编译器应该优先选择它才对,但Clang主干版本却会拒绝这段代码,一般会报“调用f有歧义”或者无法判定哪个重载更合适的错误。
问题出在Clang对可变参数模板约束的“更具约束性”判断逻辑上。按照C++标准的要求,判断两个可变参数模板重载的约束谁更严格,需要看约束的合取关系:floating_point... Ts等价于所有参数都满足floating_point(也就是(floating_point<Ts> && ...)),而arithmetic... Ts等价于所有参数都满足arithmetic((arithmetic<Ts> && ...))。
理论上,因为每个满足floating_point<T>的类型必然满足arithmetic<T>,所以(floating_point<Ts> && ...)这个合取约束是严格蕴含(arithmetic<Ts> && ...)的,第二个重载应该被判定为更具约束性。但Clang主干版本在处理这种可变参数展开的约束比较时,没有正确地逐个参数推导蕴含关系,而是把整个可变参数约束当成了一个无法拆分的整体,导致它无法识别出第二个重载的约束比第一个更严格,最终认为两个重载是歧义的,从而拒绝编译。
这种情况属于Clang在C++20概念约束实现上的一个细节问题,目前在GCC等其他编译器上是可以正确处理这段代码的。
备注:内容来源于stack exchange,提问作者yaito3014

