为什么 requires-expression 在检查私有成员访问的模板和非模板中表现不同?

Why does requires-expression behave differently in template and not-template for checking private member access?

在下面的代码中classA有一个私有成员函数f。我想写一个静态断言来检查这个函数是否可以从当前上下文访问(正如 this question 的评论中所建议的那样)。

有3个相似的案例都是基于requires-expression:

class A{ void f(); };

// #1: accepted by all
static_assert( ![](auto x){ return requires(decltype(x) a){ a.f(); }; }(A{}) );

// #2: rejected by Clang:
static_assert( ![](auto){ return requires(A a){ a.f(); }; }(nullptr) );

// #3: rejected by all
static_assert( ![](void*){ return requires(A a){ a.f(); }; }(nullptr) );

案例 #3(和 Clang 中的案例 #2)被拒绝,错误为:

error: 'f' is a private member of 'A'

演示:https://gcc.godbolt.org/z/Mxs4P7x8s

为什么 requires-expression 在模板和非模板中的行为不同(案例 #1 和 #3)? 哪个编译器在 #2 中是正确的?

虽然 https://eel.is/c++draft/expr.prim.req.general#1 描述了

A requires-expression provides a concise way to express requirements on template arguments

grammar of a requires-expression

requires-expression:
  requires requirement-parameter-list(opt) requirement-body

requirement-body:
  { requirement-seq }

requirement-seq:
  requirement
  requirement requirement-seq

requirement:
  simple-requirement
  [...]

simple-requirement:
  expression ;  // A simple-requirement asserts the validity of an expression.

允许 requires-expressions 包含不一定依赖于模板参数的任意表达式,这意味着以下格式正确:

constexpr bool f() { return requires(int a) { a; }; }
constexpr bool g() { return requires { 0; }; }

static_assert(f());
static_assert(g());

因此,对于在模板化实体外部声明的 requires 表达式,适用与任何表达式相同的规则,因此 #3 被所有编译器正确拒绝(不放弃访问检查控制)在 requires-expressions 中的表达式上下文中)。

#1 也被所有编译器正确实现,根据 https://eel.is/c++draft/expr.prim.req.general#5.sentence-2:

The substitution of template arguments into a requires-expression may result in the formation of invalid types or expressions in its requirements or the violation of the semantic constraints of those requirements. In such cases, the requires-expression evaluates to false; it does not cause the program to be ill-formed.

... as a.f(); 在泛型 a 的约束表达式中替换为类型 A 导致无效表达式,因为 f() 是私有的并且不可见。

最后,#2 是根据 https://eel.is/c++draft/expr.prim.req.general#5.sentence-6 的 IFNDR:

If the substitution of template arguments into a requirement would always result in a substitution failure, the program is ill-formed; no diagnostic required.

我们同样可以根据 https://eel.is/c++draft/temp.res.general#6.4 争辩说它是 IFNDR:

The program is ill-formed, no diagnostic required, if:

  • [...]
  • a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or

因此,#2 由所有编译器正确实现,但当编译器实际诊断 IFNDR 违规时,可以说它总是很好,因此 Clang 的可用性之星。