为什么在检查私有成员访问时,需要表达式在模板和非模板中表现不同?

Fed*_*dor 4 c++ templates language-lawyer c++20 requires-expression

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

类似的情况还有3个,都是基于requires-表达式:

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

案例 #3(以及 Clang 中的案例 #2)被拒绝并出现错误:

error: 'f' is a private member of 'A'
Run Code Online (Sandbox Code Playgroud)

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

为什么需求表达式在模板和非模板中的行为不同(案例 #1 和 #3)?#2 中哪个编译器是正确的?

dfr*_*fri 6

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

需求表达式提供了一种简洁的方式来表达对模板参数的要求

要求表达式的语法

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.
Run Code Online (Sandbox Code Playgroud)

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

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

static_assert(f());
static_assert(g());
Run Code Online (Sandbox Code Playgroud)

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

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

将模板参数替换为需求表达式可能会导致在其需求中形成无效类型或表达式,或者违反这些需求的语义约束。在这种情况下,requires-表达式的计算结果为 false;它不会导致程序格式错误。

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

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

如果将模板参数替换到需求中总是会导致替换失败,则该程序是格式错误的;无需诊断。

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

如果出现以下情况,则程序格式错误,无需诊断:

  • [...]
  • 由于不依赖于模板参数的构造,紧随其定义的模板的假设实例化将是格式错误的,或者

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