Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using concepts in an unevaluated context gives inconsistent results

Consider the following useless concept C:

template<class T>
concept C = static_cast<T>(true);

If we pass an arbitrary type to C in an unevaluated context, then all three compilers will compile successfully:

struct S {};
decltype(C<S>) x = 0;

But if we pass int to C in the unevaluated context:

decltype(C<int>) y = 0;

GCC still accepts it, while Clang and MSVC reject it with the same error message:

<source>:2:13: error: atomic constraint must be of type 'bool' (found 'int')

Is the above code still well-formed? Which compiler should I trust?

like image 400
康桓瑋 Avatar asked Aug 24 '21 15:08

康桓瑋


1 Answers

Concept names do not work on the basis of evaluating an expression as we would normally think of it. A concept name resolves to a boolean that tells if the constraint-expression is satisfied or not:

A concept-id is a prvalue of type bool, and does not name a template specialization. A concept-id evaluates to true if the concept's normalized constraint-expression is satisfied ([temp.constr.constr]) by the specified template arguments and false otherwise

Constraint expressions are broken down into atomic pieces. Fortunately, your constraint expression has only one atomic piece: static_cast<T>(true). The way we resolve whether an atomic constraint is satisfied is simple. There are several parts. Part one is:

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.

This is why the compilers allow the first one. static_cast<S>(true) is not a valid expression, as there is no conversion from a bool to an S. Therefore, the atomic constraint is not satisfied, so C<S> is false.

However, static_cast<int>(true) is a valid expression. So we move on to part 2:

Otherwise, the lvalue-to-rvalue conversion is performed if necessary, and E shall be a constant expression of type bool.

And that's where we run into the word "shall". In standard-ese, "shall" means "if the user provides code where this is not the case, there is a compile error". An int is not a "constant expression of type bool". Therefore, the code does not conform to this requirement. And a compile error results.

I imagine that GCC just treats the errors as substitution failures (that or it automatically coerces it into a bool), but the standard requires MSVC/Clang's behavior of erroring out.

like image 55
Nicol Bolas Avatar answered Sep 20 '22 08:09

Nicol Bolas