仅通过(相互排斥的)要求条款的差异来超载隐藏的朋友:法律或违反ODR



考虑以下类模板,其中包含同一友元(相同的函数类型;见下文)的两个(隐藏的)友元声明,它也定义了友元(因此友元是内联的),但定义以(互斥)requires-子句为条件:

#include <iostream>
struct Base {};
template<int N>
struct S : public Base {
friend int foo(Base&) requires (N == 1) { return 1; }
friend int foo(Base&) requires (N == 2) { return 3; }
};

[dcl.fct]/8 指出尾随requires-子句不是函数类型的一部分 [强调我的]:

返回类型、

参数类型列表、ref-qualifier、cv-qualifier-seq 和异常规范,但不是默认参数([dcl.fct.default])或尾随 requires-子句([dcl.decl]),是函数类型的一部分

这意味着上述两个定义对于两个定义都实例化的情况是违反 ODR;如果我们只关注单个翻译单元,则会违反 [basic.def.odr]/1:

任何翻译单元都不得包含任何变量、函数、类类型、枚举类型、模板、参数的默认参数(对于给定范围内的函数)或默认模板参数的多个定义。

在单个 TU 中,这种违规可以说是可诊断的(对于"格式不正确的 NDR"没有"需要")。我正在尝试了解何时实例化上述定义的规则;或者如果这完全是实现定义的(甚至在达到实例化阶段之前格式不正确)。

Clang和GCC(1)都接受以下程序

// ... as above
// (A)
int main() {
S<1> s1{};
std::cout << foo(s1);  // Clang & GCC: 1
}

但是,对于下面的程序 (B) 到 (D),Clang 接受所有程序,而 GCC 拒绝所有程序并显示重新定义错误:

// (B)
int main() {
S<1> s1{};
S<2> s2{};  // GCC: re-definition error of 'foo'
}
// (C)
int main() {
S<1> s1{};
S<2> s2{};  // GCC: re-definition error of 'foo'
std::cout << foo(s1);  // Clang: 1
}
// (D)
template struct S<1>;
template struct S<2>;  // GCC: re-definition error of 'foo'
int main() {}

只有当实际尝试通过 ADL 在两个专用化上调用 friend 函数时,Clang 才会真正发出错误

// (E)
int main() {
S<1> s1{};
S<2> s2{};  // GCC: re-definition error of 'foo'
std::cout << foo(s1); // MSVC: ambiguous call
std::cout << foo(s2);  
// Clang error: definition with same mangled name
//              '_Z3fooR4Base' as another definition
} 

我们可能会注意到,只有 MSVC 实际上达到了似乎接受这两个定义的状态,之后它按预期失败("模棱两可的调用")。

演示。

问题

类模板
  • 的隐藏非模板友元函数(友元声明,其中友元也在类中定义)是否可以仅通过(互斥)requires-子句的差异而重载?

因此,哪个编译器就在这里?

  1. 所有(格式不正确的 NDR 和/或实现定义的实例化点规则),
  2. 海湾合作委员会
  3. MSVC
  4. 无(示例 (E) 格式正确)

我无法理解当一个也是定义的友元函数声明(类模板的)被实例化时,特别是当涉及要求子句,这些规则是什么规则;但是,如果上面的GCC和Clang的行为都是不正确的,这可能无关紧要。

<小时 />

(1) 海湾合作委员会头 11.0.0,叮当头 12.0.0。

from over#dcl-1,

如果两个同名的函数声明位于同一作用域中,并且具有等效的参数声明 ([over.load]) 和等效的 ([temp.over.link]) 尾随 requires-子句(如果有的话),则它们引用同一个函数([dcl.decl])。

[注1:由于约束表达式是未计算的操作数,因此等价比较表达式而不求值它们.
[示例1:
template<int I> concept C = true;
template<typename T> struct A {
void f() requires C<42>; // #1
void f() requires true; // OK, different functions
};
— 结束示例]
— 尾注]

我知道有 2 种不同的foo(因此没有违反 ODR),因为不同的要求条款。

我认为所有提到的编译器都存在问题,无法涵盖此极端情况。

不同的尾随要求子句将不同专业的隐藏朋友的声明彼此区分开来

Clang和GCC拒绝该计划都是错误的。正如 @Jared42:s 回答中所指出的,[over.dcl]/1 同样应该适用于隐藏的友元声明,这样 OP:s 示例中的声明声明了不同的友元函数。

相关的 Clang 错误报告:

  • 错误 48872 - 拒绝对具有互斥要求子句的重载隐藏非模板友元函数有效

相关的 GCC 错误报告:

  • 错误 98822 - 拒绝有效:类模板的实例化实例化(所有)具有未满足约束的约束的非模板友元定义(甚至那些)
<小时 />

详情

海湾合作委员会因违反 [临时朋友]/9:

带有要求条款的非模板友元声明应为定义。具有依赖于封闭模板中的模板参数的约束的友元函数模板应为定义。 此类受约束的友元函数或函数模板声明不会声明与任何其他作用域中的声明相同的函数或函数模板。

起初我并不清楚这是否将朋友声明与专门化要求条款分开,但正如该段作者(Hubert Tong;详见下文)所评论的那样,这就是该段的意图:

[...]"不声明"的措辞意味着每个专业声明的朋友都是独一无二的。

Clang违反了[defns.signature.friend]是错误的,该子句在非模板友元函数的签名中包含一个尾随的要求子句(如果有的话):

⟨具有尾随 requires 子句的非模板友元函数⟩名称、参数类型列表、封闭类和尾随requires-子句

这意味着 Clang 不应该为两个单独的(通过单独的要求条款)朋友声明生成相同的损坏名称。

MSVC 也可能在过载解决阶段(具有模糊性错误)对失败程序 (E) 是错误的,例如foo(s1)应该只将S<1>候选人添加到其候选人职能中。约束检查是否真的可以应用于Base&而不是S的特定专用化的参数是另一个问题,但可能的错误不应该是歧义,而是无法满足候选函数的约束。

US115:隐藏的非模板好友需要一个要求条款

P2103R0 US115(NB在2020年2月(布拉格)会议上评论的核心语言变化)建议更新非模板隐藏好友的标准规则,以便(也)应允许非模板好友(函数)使用尾随要求子句:

美国115.隐藏的非模板好友需要一个要求条款

  1. 在 3.20 [defns.signature] 之后添加以下内容:

3.21 [defns.signature.friend]

签名

‹非模板友元函数,带有尾随 requires-clause› 名称、参数类型列表 (9.3.3.5 [dcl.fct])、封闭类和尾随要求条款(9.3 [dcl.decl])

  1. 3.21 [defns.signature.templ] 之后添加以下内容:

3.23 [defns.signature.templ.friend]

签名

‹带有涉及封闭模板参数的约束的好友函数模板› 名称,参数类型列表 (9.3.3.5 [dcl.fct]), 返回类型、封闭类、模板头尾随 要求条款

  1. 将 13.7.4 [临时朋友] 第 9 段修改如下:

非模板友语声明不得有要求条款的定义。友元函数模板 具有依赖于模板参数的约束 附上模板应为定义。如此拘谨的朋友 函数或函数模板声明不声明相同 函数或函数模板作为任何其他范围内的声明。

大多数变化影响了扩展 [临时朋友]/9.

US115 在 cplusplus/nbballot 中被记录为第 114 期:

US115 13.6.4 [临时朋友] 隐藏的非模板好友需要一个要求条款

非模板的隐藏好友目前不能有 requires 子句,但此功能很重要,并且在整个 Range 中使用。

提议的更改:

更改 [temp.friend]/9 以仅引用那些不是任何类型的模板化实体的友元声明。

并在标准草案的拉取请求 #3782 中实现,特别是根据以下提交:

NB US 115 (C++20 CD):隐藏的非模板好友需要必要条款

在提及尾随时添加了明显缺失(如果有的话) 友元函数签名定义中的 requires 子句 模板。

我要求澄清(鉴于 GCC、Clang、MSVC 的不同实现)关于 [temp.friend]/9 w.r.t. 重载隐藏的非模板好友的扩展规则,仅基于尾随要求子句的差异,答案是这应该(可能)是合法的,并且 GCC 和 Clang 以各自的方式拒绝示例 (E) 都是错误的(这应该是一个重载解决歧义错误:

汤家辉(休伯特-重新演绎)

我认为 MSVC 在这里是正确的。关于 Clang 行为,签名的描述表明该重整应该是唯一的。关于海湾合作委员会的行为,"不声明"的措辞意味着每个专业声明的朋友都是独一无二的。

最新更新