17

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?

Fedor
  • 17,146
  • 13
  • 40
  • 131
  • 3
    That's a really interesting question and case you found. – bolov Sep 14 '21 at 06:59
  • 3
    For `g`, there are no 2 atomic constraints, but 1. (consumption only applies to concept). – Jarod42 Sep 14 '21 at 08:04
  • 1
    @Jarod42 - I'm no expert on constraint normalization, but this note suggests what you say is impossible https://timsong-cpp.github.io/cppwp/n4868/temp#constr.atomic-note-1 – StoryTeller - Unslander Monica Sep 14 '21 at 21:53
  • 2
    @StoryTeller-UnslanderMonica `(sizeof(T)>0 || sizeof(U)>0)` is not a _logical-or-expression_, it is a _primary-expression_, so the Note doesn't help here (and is it necessary to open the whole `/temp` to link the Note?). However, to my reading, there are 2 atomic constraints in `g`. – Language Lawyer Sep 14 '21 at 22:32
  • I think the `sizeof(void)` makes this invalid. `void g() requires(sizeof(T)==0) {}` is invalid too – Botond Horváth Jan 07 '22 at 02:27
  • 1
    This was a bug in Clang, and already fixed in trunk: https://github.com/llvm/llvm-project/issues/51708 – Fedor Oct 05 '22 at 19:45

1 Answers1

2

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; }) {}
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Thanks. This bug in Clang looks fixed in trunk by now: https://github.com/llvm/llvm-project/issues/48857 – Fedor Oct 05 '22 at 19:46