解决requires子句中的函数重载

303*_*303 9 c++ language-lawyer c++-concepts c++20

c在尝试满足type 的概念时,Clang 和 GCC 之间似乎存在差异s。当不得不求助于 ADL 来寻找可行的 function 重载时f,Clang 似乎认为重载的自依赖性f(c auto)是一个硬错误。然而,GCC 似乎认为这是替换失败,只是丢弃候选而不是拒绝代码。

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
Run Code Online (Sandbox Code Playgroud)

这段代码是否应该被拒绝并导致硬错误?C++20 标准对这种情况有何规定?

演示


铿锵错误消息:

<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
Run Code Online (Sandbox Code Playgroud)

MSVC 错误消息:

<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>'
Run Code Online (Sandbox Code Playgroud)

Jan*_*tke 5

MSVC 和 Clang 拒绝此代码是正确的,而 GCC 接受它是一个错误或扩展。clang 中的错误消息的第一行解释了这个问题:

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

要查看调用是否f(t)有效(其中t类型为 )sf(c auto)必须作为候选者进行检查。

  • f(c auto)是一个可行的候选人,如果s满足c
  • s满足ciff(t)格式良好
  • f(t)f(c auto)如果是可行的候选人,则格式良好
  • f(c auto)是一个可行的候选人,如果s满足c
  • ...

显然,这里存在无限递归。据我所知,当涉及到这种无限递归情况时,C++ 标准不包含明确的措辞,除了会被它打破的实现限制之外。

这个漂亮的“依赖于自身”的错误也是相当新的。例如,clang 15 及更早版本将输出:

致命错误:递归模板实例化超出最大深度 1024

海湾合作委员会

关于重载解析的措辞非常清楚:

  • 首先,选择候选函数的子集(具有适当数量的参数并满足某些其他条件的函数)以形成一组可行的函数
  • 然后,根据将每个自变量与每个可行函数的相应参数匹配所需的隐式转换序列来选择最佳可行函数。

- [over.match.general] p2

其次,为了使函数可行,如果它具有关联的约束则应满足这些约束[temp.constr.constr])。

- [over.match.viable] p3

我的理论是,GCC 通过跳到第二个项目符号来打破递归,并将其f(c auto)视为不相关,因为f(s)存在,这是一个更好的候选者,无论f(c auto)是否可行。GCC 似乎从不检查是否f(c auto)满足约束条件,尽管这是格式良好的程序所必需的。

如果删除f(s)重载,所有三个编译器都会引发错误,并且 GCC 也会抱怨:

<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); };
      |             ^~~~~~~~~~~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

编译器可以接受格式错误的程序作为扩展,因此这可以说是合规的。这是故意的还是错误是另一个问题。