仅在(互斥的)需要条款中的差异导致隐藏的朋友超载:合法还是违反 ODR?

dfr*_*fri 8 c++ templates friend-function language-lawyer c++20

考虑下面的类模板,它包含同一个朋友(相同的函数类型;见下文)的两个(隐藏)朋友声明,它也定义了朋友(因此朋友是内联的),但定义条件为(互斥)要求条款

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

[dcl.fct] / 8指出尾随需要子句函数的类型的一部分。重点矿]:

返回类型、参数类型列表、引用限定符、cv-qualifier-seq 和异常规范,但不是默认参数([dcl.fct.default])或尾部的 requires-clause ([ 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
}
Run Code Online (Sandbox Code Playgroud)

然而,对于下面的程序 (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() {}
Run Code Online (Sandbox Code Playgroud)

只有在实际尝试通过 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
} 
Run Code Online (Sandbox Code Playgroud)

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

演示

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

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

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

我一直无法理解管辖什么样的规则,当一个朋友函数声明(类模板),这也是一个定义,实例化,特别是当需要子句参与; 不过,如果上述 GCC 和 Clang 的行为都不正确,则这可能无关紧要。


(1) GCC HEAD 11.0.0,Clang HEAD 12.0.0。

Jar*_*d42 6

over#dcl-1

两个同名的函数声明如果它们在相同的范围内并且具有等效的参数声明 ([over.load]) 和等效的 ([temp.over.link]) 尾随 requires-clauses,如果有的话 ( [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 违规),因为需要不同的条款。

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