仅在(互斥的)要求子句中因差异而使隐藏好友超载:合法或违反 ODR?

Overloading of hidden friends by differences only in (mutually exclusive) requires-clauses: legal or an ODR-violation?

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

#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-clausesnot 函数类型的一部分 [emphasis 我的]:

The return type, the parameter-type-list, the ref-qualifier, the cv-qualifier-seq, and the exception specification, but not the default arguments ([dcl.fct.default]) or the trailing requires-clause ([dcl.decl]), are part of the function type.

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

No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, template, default argument for a parameter (for a function in a given scope), or default template argument.

并且在单个 TU 中,这种违规行为可以说是可诊断的(对于“格式错误,NDR”没有“必要”)。我正在尝试理解 when 上面的定义将被实例化的规则;或者如果这完全是实现定义的(或者甚至在到达实例化阶段之前格式错误)。

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 调用友元函数时,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 实际上达到了一种看似接受两个定义的状态,之后它会按预期失败(“模糊调用”)。

DEMO.

问题

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

  1. 所有(格式错误的 NDR and/or 实现已定义 w.r.t。实例化规则点),
  2. 海湾合作委员会
  3. 铿锵
  4. MSVC
  5. None(示例 (E) 格式正确)

我无法理解在 友元函数声明(属于 class 模板)实例化时,它也是一个定义,特别是当涉及 requires-clauses 时;不过,如果上述 GCC 和 Clang 的行为都不正确,这可能是无关紧要的。


(1) GCC 头 11.0.0,Clang 头 12.0.0。

来自 over#dcl-1

Two function declarations of the same name refer to the same function if they are in the same scope and have equivalent parameter declarations ([over.load]) and equivalent ([temp.over.link]) trailing requires-clauses, if any ([dcl.decl]).

[Note 1: Since a constraint-expression is an unevaluated operand, equivalence compares the expressions without evaluating them.
[Example 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
};
— end example]
— end note]

我知道有 2 个不同的 foo(因此没有违反 ODR),因为需要的子句不同。

我认为所有提到的编译器都存在问题,无法涵盖这种特殊情况。

不同的尾随 requires-clauses 将不同专业的隐藏好友的声明与彼此区分开来

Clang和GCC拒绝程序都是错误的。正如 @Jared42:s answer, [over.dcl]/1 中所指出的,同样应适用于隐藏的友元声明,以便 OP:s 示例中的声明声明不同的友元函数。

相关的 Clang 错误报告:

相关的 GCC 错误报告:


详情

GCC 因违反 而出错 [temp.friend]/9:

A non-template friend declaration with a requires-clause shall be a definition. A friend function template with a constraint that depends on a template parameter from an enclosing template shall be a definition. Such a constrained friend function or function template declaration does not declare the same function or function template as a declaration in any other scope.

一开始我并不清楚这会将友元声明与 specialization-exclusive requires-clauses 分开,但正如该段作者(Hubert Tong;详见下文)所评论的那样,这是段落的意图:

[...] the "does not declare" wording is meant to say that the friends declared by each specialization is unique.

Clang 因违反 [defns.signature.friend] 而出错,其中包括 non-template 友元函数签名中的尾随 requires-clause(如果有)[强调我的]:

⟨non-template friend function with trailing requires-clause⟩ name, parameter-type-list, enclosing class, and trailing requires-clause

意味着 Clang 不应为这两个单独(通过单独的 requires-clauses)友元声明生成相同的损坏名称。

MSVC 可能 在重载解析阶段(带有歧义错误)的失败程序(E)也是错误的,例如foo(s1) 可以说应该只将 S<1> 中的候选函数添加到其候选函数中。约束检查是否实际上可以应用于 Base& 而不是 S 的特定专业化的参数是另一个问题,但可能的错误不应该是歧义之一,而是无法满足约束一个候选函数。

US115:隐藏non-template朋友需要一个requires-clause

P2103R0 的 US115(在 2020 年 2 月(布拉格)会议上对 NB 评论的核心语言更改)提议更新 non-template 的标准规则隐藏的朋友,这样(也)non-template 朋友(函数)应该被允许使用尾随 requires-clauses:

US115. Hidden non-template friends need a requires-clause

  1. Add the following after 3.20 [defns.signature]:

3.21 [defns.signature.friend]

signature

‹non-template friend function with trailing requires-clause› name, parameter-type-list (9.3.3.5 [dcl.fct]), enclosing class, and trailing requires-clause (9.3 [dcl.decl])

  1. 在3.21后添加如下[defns.signature.templ]:

3.23 [defns.signature.templ.friend]

signature

‹friend function template with constraint involving enclosing template parameters› name, parameter-type-list (9.3.3.5 [dcl.fct]), return type, enclosing class, template-head, and trailing requires-clause

  1. 将13.7.4 [temp.friend]第9段修改如下:

A non-template friend declaration shall not have with a requires-clause shall be a definition. A friend function template with a constraint that depends on a template parameter from an enclosing template shall be a definition. Such a constrained friend function or function template declaration does not declare the same function or function template as a declaration in any other scope.

大多数变化的影响 [temp.friend]/9.

US115 在cplusplus/nbballot中记录为issue 114:

US115 13.6.4 [temp.friend] Hidden non-template friends need a requires-clause

Hidden friends that are non-templates currently cannot have a requires-clause, but this functionality is important and used throughout Ranges.

Proposed change:

Change [temp.friend]/9 to refer only to those friend declarations that are not any kind of templated entity.

并在 pull request #3782 to the standard draft, particularly as per the following commit 中实施:

NB US 115 (C++20 CD): Hidden non-template friends need a requires-clause

Added obviously-missing (if any) to the mention of a trailing requires-clause in the definition of signature for a friend function template.

I asked for clarification(考虑到 GCC、Clang、MSVC 的不同实现)关于 [temp.friend]/9 w.r.t 的扩展规则。重载隐藏 non-template friends 仅基于尾随 requires-clauses 的差异,答案是这应该(可能)是合法的,并且 GCC 和 Clang 以各自的方式拒绝示例 (E) 是错误的(应该是重载解析歧义错误:

Hubert Tong (hubert-reinterpretcast)

I think MSVC is correct here. With respect to the Clang behaviour, the description of the signature indicates that the mangling should be unique. With respect to the GCC behaviour, the "does not declare" wording is meant to say that the friends declared by each specialization is unique.