静态成员函数在声明之前在模板化类中是否可见?

Is a static member function visible inside a templated class before it is declared?

提问人:303 提问时间:10/18/2023 最后编辑:Jan Schultke303 更新时间:10/19/2023 访问量:174

问:

静态成员函数是否应该对 的默认构造函数的 requires-clause 可见?C++ 标准对所提供示例的合法性有何看法?bs

template<auto...>
struct s {
    s() requires (s::b()) = default; // clang nope, gcc ok, msvc ok
    static constexpr bool b()
    { return true; }
};
static_assert((s<>{}, true));

现场示例


来自 Clang 的错误消息:

<source>:3:22: error: no member named 'b' in 's<...>'
    3 |     s() requires (s::b()) = default;
      |                   ~~~^
<source>:7:15: error: static assertion expression is not an integral constant
expression
    7 | static_assert((s<>{}, true));
      |               ^~~~~~~~~~~~~
<source>:7:16: note: non-constexpr constructor 's' cannot be used in a constant
expression
    7 | static_assert((s<>{}, true));
      |                ^
<source>:3:5: note: declared here
    3 |     s() requires (s::b()) = default;
      |     ^
c++ 语言律师 c++20 static-members requires-子句

评论

2赞 user12002570 10/18/2023
实际上有很多重复项。
1赞 303 10/18/2023
@user12002570 是的,这个答案让我们很好地了解了编译器后台可能发生的事情,但它没有解决任何正式的标准,而这正是我特别要求的。
1赞 user17732522 10/18/2023
@user12002570 模板这一事实实际上是相关的,链接的问题没有考虑到这一点。s
2赞 user17732522 10/18/2023
@user12002570 在那个上下文中,使用是在完整的类上下文中,并且也是单独实例化的,而这里的情况并非如此。这里的问题是模板中的查找规则之一,也许是 requires-clauses 实例化的细节,而不仅仅是函数的定义。constexpr
2赞 user12002570 10/18/2023
@user17732522我认为它的格式不正确,因为“调用未定义的 constexpr 函数”不是一个常量表达式?此外,调用不在完整的类上下文中。

答:

-2赞 user12002570 10/18/2023 #1

程序的行为(格式错误)可以使用 expr.const#5 来理解,它指出:

5. 表达式 E 是一个核心常量表达式,除非按照抽象机器的规则 ([intro.execution]) 对 E 的计算将计算以下其中一项:

5.2 调用未定义的 constexpr 函数;

(强调我的)

并且由于在调用 时,静态函数是未定义的,因此表达式在该点上不是常量表达式。结合使用调用的上下文不是完整的类上下文这一事实,我们得到了提到的错误。s::b()constexprs::b()

的完整类上下文

  • 函数体 ([dcl.fct.def.general]),
  • 默认参数
  • noexcept-specifier ([except.spec]),或者
  • 默认成员初始值设定项

在类的成员规范中。


因此,该程序格式不正确。

评论

0赞 user17732522 10/18/2023
如果在实例化点已经可以通过查找找到该函数,则其定义也已经可用,因为它位于模板定义中的实例化点之前。如果查找甚至没有找到(正如 Clang 的错误消息所述),那么引用是无关紧要的。想象一下,它反而存在。这有效吗?它不需要函数调用。ss::bs::brequires (decltype(s::b()){true})constexpr
0赞 Alan 10/18/2023
@user17732522 你说“那么它的定义也已经可用了”。但我认为这不一定是真的,因为成员函数的定义只有在使用它们(成员函数)时才会实例化。
0赞 user17732522 10/18/2023
@Alan“调用未定义的 constexpr 函数”有点含糊不清,但这并不意味着函数的实例化点应该在使用点之前,因为函数的实例化点总是在导致隐式实例化的第一次使用之后。这将使模板化函数在实践中无法使用。它只能引用定义点。constexprconstexpr
2赞 Brian Bi 10/19/2023 #2

简而言之,不会找到成员名称,因为它是在命名点之后声明的,并且 requires-clause 不是完整的类上下文。[class.mem.general]/7 定义了一组完整的类上下文,[class.member.lookup]/3 解释了在名称查找期间可以找到哪些类成员:bb

声明集是在 C 范围内从 C 的类说明符之后立即搜索 N 的结果,如果 P 位于 C 的完整类上下文中,则从 P 进行单次搜索。如果生成的声明集不为空,则子对象集包含 C 本身,并且计算完成。

由于 requires-clause 不是完整的类上下文,因此从命名点开始进行单个搜索。的声明不在该点之前,因此单个搜索 ([basic.lookup.general]/3) 找不到它。bb

使此分析复杂化的事实是,模板可以包含依赖名称,而这些依赖名称受不同规则的约束。但是,不是依赖名称,因为它的查找上下文引用当前实例化。请参见 [temp.dep.type]/5s::b

限定名称 ([basic.lookup.qual]) 是依赖于以下情况

  • 它是一个 conversion-function-id,其 conversion-type-id 是依赖的,或者
  • 它的查找上下文是依赖的,不是当前实例化,或者
  • 它的查找上下文是当前实例化,它是 或operator=
  • 它的查找上下文是当前实例化,并且至少有一个依赖基类,并且该名称的限定名称查找未找到任何内容 ([basic.lookup.qual]) 。

在这些情况下,名称查找需要推迟到实例化时间。在这种情况下,由于查找上下文是当前实例化,因此不需要延迟名称查找,除非它找到的声明可能在依赖基类中(在这种情况下,您需要知道模板参数,然后才能知道是否在基类中)。在这种情况下,不是依赖名称,因为没有基类。在 [temp.res.general]/1 下,不会延迟对非依赖名称的名称查找。所以它什么也找不到(因为它不是在完整的类上下文中),并且程序格式不正确。sbbs::b

所以我认为 GCC 和 MSVC 这里有一个错误,尽管因为模板中的名称查找是一个非常棘手的问题,所以我可能遗漏了一些东西。