Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Constraints in C++20 are normalized before checked for satisfaction by dividing them on atomic constraints. For example, the constraint E = E1 || E2 has two atomic constrains E1 and E2

And substitution failure in an atomic constraint shall be considered as false value of the atomic constraint.

If we consider a sample program, there concept Complete = sizeof(T)>0 checks for the class T being defined:

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
}

then the function f<void,int>() satisfies the requirements, because Complete<void> just evaluates to false due to substitution failure and Complete<int> evaluates to true.

But a similar function g<void,int>() makes the compilers diverge. GCC accepts it, but Clang does not:

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

Demo: https://gcc.godbolt.org/z/zedz7dMGx

Are the functions f and g not really identical, or Clang is wrong here?

like image 874
Fedor Avatar asked Sep 14 '21 06:09

Fedor


1 Answers

This is Clang bug #49513; the situation and analysis is similar to this answer.

sizeof(T)>0 is an atomic constraint, so [temp.constr.atomic]/3 applies:

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 is an invalid expression, so that constraint is not satisfied, and constraint evaluation proceeds to sizeof(U)>0.

As in the linked question, an alternative workaround is to use "requires requires requires"; demo:

template<class T, class U>
void g() requires(requires { requires sizeof(T)>0; } || requires { requires sizeof(U)>0; }) {}
like image 54
ecatmur Avatar answered Oct 07 '22 15:10

ecatmur