Clang 拒绝以下代码:

#include <concepts>

struct k { static void f(); };
// clang nope, gcc ok, msvc ok
static_assert(std::same_as<decltype(k::f), decltype(k{}.f)>);

这似乎可以归结为是否decltype(k{}.f)应将其检查为类型void()、类型void (&)()甚至类型void (&&)()。当使用括号k{}.f(即decltype((k{}.f)):)时,Clang 和 GCC 都同意类型void (&)(),而 MSVC 将表达式视为类型void (&&)()。这似乎表明 GCC 和 MSVC 都不将 plain 的类型检查视为k{}.f左值表达式。

decltype我推测在检查 形式的静态成员函数时没有特殊例外k{}.f。因此,这是否意味着 GCC 和 MSVC 都不符合要求?

5

  • 您能添加编译器的版本吗?


    – 

  • 1
    k::f对于非静态成员函数来说,这是无效语法。Clangs 似乎将这种无效性扩展到了静态成员函数。因此,decltype(k::f)无法推断。可能是一个错误。


    – 


  • 1
    @RobertPrévost 帖子中有一个在线演示的链接,其中使用了每个编译器的最新稳定版本以及主干版本。


    – 


  • @3CxEZiVlQ 为什么你说decltype(k::f)无法推断?所有编译器似乎都将其推断为类型void()


    – 

  • 我谈到了非静态成员函数。k::f在 C++ 中,如果 id 表达式是非f静态成员函数,则为无效。&k::f是有效的。


    – 



最佳答案
1

这些decltype 说明符受管辖

否则,如果E是未带括号的id 表达式或未带括号的类成员访问 ([expr.ref]),则decltype(E是E)命名的实体的类型。如果不存在这样的实体,则程序格式不正确;

k::f是一个不带括号的id 表达式,并且它命名的函数具有类型void()

那么k{}.f,不带括号的类成员访问呢?此表达式受约束:

如果E2是重载集,则表达式应为成员函数调用 ([expr.call]) 的(可能带括号的)左操作数,并且函数重载解析 ([over.match]) 用于选择所E2引用的函数。 的类型E1.E2是 的类型E2,并E1.E2引用 所引用的函数E2。[…]

重点是我加的。粗体字眼k{}.f在不用于调用函数时使表达式格式不正确k::f。请注意,即使f查找只找到一个名为的函数,它仍被视为重载集()。

)将此种表达式标记为格式错误。似乎还没有编译器实现它。这个核心问题的动机可能是围绕处理此类表达式的实现分歧。

在添加粗体措辞之前,末尾的措辞将决定结果:E1.E2指的是 所引用的函数E2,即函数k::f,因此您将再次得到void()类型。

5

  • 保留“如果 E2 引用静态成员函数,则 E1.E2 是左值”这句话有什么意义呢?


    – 

  • 1
    @Barry 据我所知,我们所说的值类别是什么并不重要。但是,我们确实在 [basic.lval] 中声明每个表达式都是左值、右值或纯右值。


    – 

  • DR 建议修改标准中的措辞,以明确表示如果这是预期结果void (*q)() = y.f;,则表达式应该是格式错误的。这似乎表明有空间决定不将此类表达式视为错误。也可以通过更改工作方式以允许此类表达式来解决实现分歧。那么,最终拒绝这一决定的理由是什么?


    – 

  • @303 我认为有些人认为之前的措辞已经禁止了这种行为,在这种情况下,CWG2725 中的更改仅仅是一种澄清。


    – 

  • @BrianBi 他们选择这条路确实有点奇怪,因为从元编程的角度来看,现在事情变得有点笨拙。例如:template<auto K> decltype((K.f)) g();现在必须变成:template<auto K> decltype((decltype(K)::f)) g();。我觉得有一些余地可以争论说,这种变化使事情变得更加复杂,而没有真正的好处。


    –