Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolving function overload in requires clause

There seems to be a discrepancy between Clang and GCC when attempting to satisfy concept c for type s. When having to resort to ADL to look for a viable overload of function f, Clang seems to consider the self-dependency of overload f(c auto) to be a hard-error. Whereas, GCC seems to consider it to be a substitution failure, simply discarding the candidate instead of rejecting the code.

void f(int);

template<typename T>
concept c = requires(T t) { f(t); };

struct s {};
void f(s);
void f(c auto);

static_assert(c<s>); // clang nope, gcc ok, msvc nope

Should this code be rejected and result in a hard-error? What does the C++20 standard say about this scenario?

Demo


Clang error message:

<source>:4:13: error: satisfaction of constraint 'requires (T t) { f(t); }'
depends on itself
    4 | concept c = requires(T t) { f(t); };
      |             ^~~~~~~~~~~~~~~~~~~~~~~
<source>:4:13: note: while substituting template arguments into constraint
expression here
    4 | concept c = requires(T t) { f(t); };
      |             ^~~~~~~~~~~~~~~~~~~~~~~
<source>:8:8: note: while checking the satisfaction of concept 'c<s>'
requested here
    8 | void f(c auto);
      |        ^
<source>:8:8: note: while substituting template arguments into constraint
expression here
    8 | void f(c auto);
      |        ^
<source>:4:29: note: while checking constraint satisfaction for template 'f<s>'
required here
    4 | concept c = requires(T t) { f(t); };
      |                             ^
<source>:4:29: note: in instantiation of function template specialization 'f<s>'
requested here
<source>:4:29: note: in instantiation of requirement here
    4 | concept c = requires(T t) { f(t); };
      |                             ^~~~
<source>:4:13: note: while substituting template arguments into constraint
expression here
    4 | concept c = requires(T t) { f(t); };
      |             ^~~~~~~~~~~~~~~~~~~~~~~
<source>:10:15: note: while checking the satisfaction of concept 'c<s>'
requested here
   10 | static_assert(c<s>);
      |               ^~~~
<source>:10:15: error: static assertion failed
   10 | static_assert(c<s>);
      |               ^~~~
<source>:10:15: note: because substituted constraint expression is ill-formed:
constraint depends on a previously diagnosed expression

MSVC error message:

<source>(8): error C7608: atomic constraint should be a constant expression
<source>(10): note: see reference to variable template 'bool c<s>' being compiled
<source>(8): error C2131: expression did not evaluate to a constant
<source>(8): note: failure was caused by a read of an uninitialized symbol
<source>(8): note: see usage of 'c<s>'
like image 249
303 Avatar asked May 27 '26 20:05

303


1 Answers

MSVC and Clang are correct in rejecting this code, and GCC accepting it is a bug or extension. The first line of the error message in clang explains the matter:

<source>:4:13: error: satisfaction of constraint 'requires (T t) { f(t); }'
depends on itself
    4 | concept c = requires(T t) { f(t); };
      |             ^~~~~~~~~~~~~~~~~~~~~~~

To see whether the call f(t) is valid where t is of type s, f(c auto) has to be examined as a candidate.

  • f(c auto) is a viable candidate if s satisfies c
  • s satisfies c if f(t) is well-formed
  • f(t) is well-formed if f(c auto) is a viable candidate
  • f(c auto) is a viable candidate if s satisfies c
  • ...

Obviously, there is infinite recursion here. To my knowledge, the C++ standard doesn't contain explicit wording when it comes to such infinite recursion cases, other than having implementation limits that would be blown by it.

The pretty "depends on itself" error is also quite new. For example, clang 15 and older would output:

fatal error: recursive template instantiation exceeded maximum depth of 1024

GCC

The wording on overload resolution is quite clear:

  • First, a subset of the candidate functions (those that have the proper number of arguments and meet certain other conditions) is selected to form a set of viable functions.
  • Then the best viable function is selected based on the implicit conversion sequences needed to match each argument to the corresponding parameter of each viable function.

- [over.match.general] p2

Second, for a function to be viable, if it has associated constraints, those constraints shall be satisfied ([temp.constr.constr]).

- [over.match.viable] p3

My theory is that GCC breaks the recursion by jumping ahead to the second bullet and dismissing f(c auto) as irrelevant because f(s) exists, which is a better candidate, no matter whether f(c auto) is viable or not. GCC seemingly never checks whether f(c auto) has constraint satisfaction, despite this being required for a well-formed program.

If you remove the f(s) overload, all three compiler raise an error and GCC also complains:

<source>:4:13: error: satisfaction of atomic constraint
'requires(T t) {f(int)(t);} [with T = T]' depends on itself
    4 | concept c = requires(T t) { f(t); };
      |             ^~~~~~~~~~~~~~~~~~~~~~~

Compilers are allowed to accept ill-formed programs as an extension, so this is arguably compliant. Whether it's intentional or a bug is another question.

like image 178
Jan Schultke Avatar answered May 30 '26 08:05

Jan Schultke