模板函数 requires 子句的原子约束中的替换失败

Substitution failure in an atomic constraint of template function requires-clause

C++20 中的约束在通过将它们划分为原子约束来检查满意度之前进行规范化。例如,约束 E = E1 || E2 有两个原子约束 E1E2

并且原子约束中的替换失败应被视为原子约束的假值。

如果我们考虑一个示例程序,concept Complete = sizeof(T)>0 检查是否定义了 class T

template<class T>
concept Complete = sizeof(T)>0; 

template<class T, class U>
void f() requires(Complete<T> || Complete<U>) {}

template<class T, class U>
void g() requires(sizeof(T)>0 || sizeof(U)>0) {}

int main() { 
    f<void,int>(); //ok everywhere
    g<void,int>(); //error in Clang
}

那么函数 f<void,int>() 满足要求,因为 Complete<void> 由于替换失败而计算为 falseComplete<int> 计算为 true.

但是一个相似的函数g<void,int>()使得编译器产生了分歧。 GCC 接受它,但 Clang 不接受:

error: no matching function for call to 'g'
note: candidate template ignored: substitution failure [with T = void, U = int]: invalid application of 'sizeof' to an incomplete type 'void'
void g() requires(sizeof(T)>0 || sizeof(U)>0) {}

演示:https://gcc.godbolt.org/z/zedz7dMGx

函数 fg 不是完全相同,还是这里的 Clang 是错误的?

这是Clang bug #49513; the situation and analysis is similar to

sizeof(T)>0 是一个 原子约束 ,因此 [temp.constr.atomic]/3 适用:

To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression, the constraint is not satisfied. [...]

sizeof(void)>0 是一个无效的表达式,因此不满足约束条件,并且约束评估继续进行到 sizeof(U)>0.

与链接问题一样,另一种解决方法是使用“requires requires requires”; demo:

template<class T, class U>
void g() requires(requires { requires sizeof(T)>0; } || requires { requires sizeof(U)>0; }) {}