c++language-lawyerc++20voidrequires-expression

Requires expression with local parameter of void type


A requires-expression can introduce local parameters using a parameter list. If any of these parameters has void type, will the requires expression just yield false or shall it be hard compilation error?

Consider example #1:

template<typename T>
concept C = requires(T t, void v) {
    t;
};

static_assert(!C<int>);

which is accepted by both Clang and MSVC, but GCC complains

error: invalid use of type 'void' in parameter declaration

Online demo: https://gcc.godbolt.org/z/voMajE4K3

Shorter example #2:

static_assert( requires(int t, void) {t;} );

is again accepted by Clang, where requires yields true. GCC issues the same error. And MSVC now prints:

error C2860: 'void' cannot be used as a function parameter except for '(void)'

Online demo: https://gcc.godbolt.org/z/eYbM49d7E

Which compiler is correct in both examples above?


Solution

  • Edited on Sep 20, 2024 to correct factual errors

    The current language rules state that a requires-expression is false if one of its requirements is ill-formed, but this doesn't extend to the parameter-declaration-clause. Some people expect that an ill-formed construct in the parameter-declaration-clause also makes the requires-expression false, and there is an open CWG issue asking to change the language specification accordingly (CWG2565).

    However, if you actually try to evaluate C<int>, what happens is a constraint satisfaction check on the definition of C with T = int ([temp.names]/9). A requires-expression is an atomic constraint. When checking whether an atomic constraint is satisfied, the entire atomic constraint is a SFINAE context: if anything in the immediate context of the atomic constraint is ill-formed, then the atomic constraint is considered to not be satisfied ([temp.constr.atomic]/3). So, it would appear that the OP's program is fine actually: C<int> should just be false. (Note that this is the reason why, in the description of CWG2565, using a concept fails to actually illustrate the issue. You have to use a variable template instead, so that you don't introduce an enclosing SFINAE context.)

    Now, the fact that GCC rejects the program is probably due to an extension of [expr.prim.req.general]/5:

    [...] If the substitution of template arguments into a requirement would always result in a substitution failure, the program is ill-formed; no diagnostic required.

    This rule doesn't apply to the parameter-declaration-clause. But since GCC and other compilers already treat ill-formed constructs in the parameter-declaration-clause the same as ones in requirements (i.e., they behave the way that CWG2565 is asking the committee to bless) it would be strange to exempt the parameter-declaration-clause from this rule, since that would somehow give it even more "favourable" treatment than the requirements. So, it may be that GCC rejects the program for the same reason that it would reject the program if you put some kind of ill-formed construct in a requirement, such as *(void*)0. But, if we assume that this is supposed to be IFNDR, then other compilers are also right to accept it.