需要 C++20 函数模板中的子句定位

Tri*_*dle 4 c++ language-lawyer c++-concepts c++20

在 C++20 中,您可以通过几种不同的方式编写约束函数模板:

template <typename T>
concept Fooable = true;

template <typename T>
    requires Fooable<T>
void do_something(T&); // (1)

template <typename T>
void do_something(T&) requires Fooable<T>; // (2)
Run Code Online (Sandbox Code Playgroud)

根据这个问题中接受的答案,这两种形式是等价的(这一直是我的理解)。

但是,我注意到 GCC 12.1 认为 (1) 和 (2) 是两个不同的函数,而不是 (2) 是重新声明:可以为两者提供定义,并且尝试调用do_something()是不明确的(示例)。

  • GCC 正确吗?这是两个不同的函数?
  • 如果是这样,两种风格之间在含义上是否存在技术差异?

编辑:

  • 正如评论中指出的,链接的问题指出函数模板声明和定义必须使用相同的“需要样式”。这个限制的原因是什么?

(我依稀记得在 Concepts TS 时代,需求经过“规范化”来决定它们何时等效——我想在 C++20 中不再是这种情况了?)

Bar*_*rry 8

这方面的措辞发生了一些变化。在 C++20 中,我们在[temp.over.link]/7中有这条规则:

  1. 如果两个函数模板在同一作用域中声明、具有相同的名称、具有等效的模板头,并且具有使用上述规则等效的返回类型、参数列表和尾随 require 子句(如果有),则它们是等效的比较涉及模板参数的表达式。如果两个函数模板在相同的作用域中声明,具有相同的名称,接受并满足同一组模板参数列表,并且具有使用上述规则在功能上等效的返回类型和参数列表,则它们在功能上等效比较涉及模板参数的表达式。如果程序的有效性或含义取决于两个构造是否等效,并且它们在功能上等效但不等效,则该程序是格式错误的,无需诊断。

  2. [注7:这一规则保证等效的声明将相互链接,同时不要求实现付出巨大的努力来保证功能等效的声明将被视为不同的。

// guaranteed to be the same
template <int I> void f(A<I>, A<I+10>);
template <int I> void f(A<I>, A<I+10>);

// guaranteed to be different
template <int I> void f(A<I>, A<I+10>);
template <int I> void f(A<I>, A<I+11>);

// ill-formed, no diagnostic required
template <int I> void f(A<I>, A<I+10>);
template <int I> void f(A<I>, A<I+1+2+3+4>);
Run Code Online (Sandbox Code Playgroud)

-尾注]

在你的例子中:

template <typename T>
    requires Fooable<T>
void do_something(T&); // (1)

template <typename T>
void do_something(T&) requires Fooable<T>; // (2)
Run Code Online (Sandbox Code Playgroud)

它们在功能上是等效的(基本上它们具有相同的约束)但不等效(它们具有不同的模板头 - 模板参数后面的 require 子句是模板头的一部分),这使得这种格式不正确不需要诊断。在实践中,因为它们不等效,所以它们是不同的重载 - 但因为它们在功能上是等效的,所以任何调用尝试在它们之间都将是不明确的。

正如我在另一个答案中指出的那样,它们具有相同的含义 - 只是如果将它们分开,则必须坚持使用一种声明和定义形式。


目前措辞,在 Davis Herring 的综合论文P1787之后,涉及到[basic.scope.scope]/4

如果两个声明(重新)引入相同的名称、都声明构造函数或都声明析构函数,则它们对应,除非 [...] 每个声明都声明一个函数或函数模板,除非 [...] 都声明具有等效非的函数模板-object-parameter-type-lists、返回类型(如果有)、模板头和尾随的 require-clauses(如果有),并且,如果两者都是非静态成员,则它们具有相应的对象参数。

这使得两个do_somethings不对应,从而使它们成为不同的函数模板。我们不会遇到新的功能等效但不等效的规则(因此我们不是格式错误,不需要诊断),但我们只有两个在所有情况下都必然不明确的函数模板。所以...不是世界上最有用的东西。