【问题标题】:Is a pointer to function (sometimes/always?) a function declarator?指向函数的指针(有时/总是?)是函数声明符吗?
【发布时间】:2021-02-02 11:42:24
【问题描述】:

(此问题已从the discussion to this answer 拆分出来,其中突出显示CWG 1892


标准的某些段落将特定规则应用于函数声明符;例如[dcl.spec.auto]/3 关于占位符类型 [emphasis mine]:

占位符类型可以出现在decl-specifier-seqtype-specifier-seq中的函数声明符 Conversion-function-id 或 trailing-return-type,在此类声明符有效的任何上下文中。如果函数声明符包含 trailing-return-type ([dcl.fct]),则 trailing-return-type 指定函数的声明返回类型。 否则,函数声明器应该声明一个函数。 [...]

限制占位符类型与函数声明符一起出现的位置。我们可以研究下面的例子:

int f() { return 0; }
auto (*g)() = f;  // #1

GCC 和 Clang 都接受,将 g 推导出为 int(*)()

  • 指向函数的指针(有时/总是?)是函数声明符吗?
  • 或者,或者,应用于该示例,是否应该根据[dcl.spec.auto]/3 拒绝#1,或者后者在此处不适用,因为指向函数的指针不是函数声明符(而是允许#1 根据@ 987654325@关于从初始化程序中推导变量类型)?

给定声明符的规则并不完全容易遵循,但我们可能会注意到,来自[dcl.decl]/1

声明器在声明中声明单个变量、函数或类型。

给定的声明符是变量声明符函数声明符类型声明符中的任何一个。

  • [dcl.ptr] 涵盖作为指针的(变量)声明符,但没有明确(/规范地)提及函数的指针,尽管在 [dcl.ptr]/4 中这样做是非规范的
  • [dcl.fct] 涵盖了函数声明符,但没有提及函数指针作为函数声明的一部分,除了注意在函数指针的赋值/初始化期间检查函数类型(这与函数声明符无关)

我的解释是#1 是合法的(根据当前标准),因为它属于变量声明符。如果这实际上是正确的,那么扩展的问题(来自链接的线程)是是否

template<auto (*g)()> 
int f() { return g(); }

是否合法(/根据 CWG 1892 是否合法);因为模板参数可以说包含一个声明符,它是一个函数指针声明符,而不是一个函数声明符。

我们最终可能会注意到,正如链接到答案中同样指出的那样,

template<auto g()>  // #2
int f() { return g(); }

可以说是格式错误的(尽管这个例子也被 GCC 和 Clang 接受),因为#2 处的非类型模板参数是一个函数声明符,因此根据 [dcl. spec.auto]/3,因为它不包含尾随返回类型,也不声明函数。

【问题讨论】:

  • 我的理解是auto (*g)() = f; 工作的原因与auto x = 42; 工作的原因相同,而不是因为它与函数有关。其实one can writeauto x = 42, (*g)() = f;
  • @IgorTandetnik 为了清楚起见:我们讨论的是格式良好的,而不是有效的(这些在语言律师问题中通常存在冲突)。我也倾向于 auto (*g)() = f 格式正确,关键是它的声明中没有函数声明符(这样 [dcl.spec.auto]/3 不适用)。你的第二个例子是[dcl.decl]/3,在这里并不重要;所讨论的单个声明符是格式正确的还是格式错误的(单独或作为带有多个声明符的声明的一部分)。
  • 它的声明中没有函数声明符 那么auto (*g)()是如何解析的呢?
  • @LanguageLawyer "倾向于 [...] 没有函数声明符" - 这是我的关键问题:这里实际上涉及函数声明符吗? / 是指向函数的指针,还是包含函数声明符?如果您有“是/确实”的论据,请考虑发布答案。 (在这种情况下 [dcl.spec.auto]/3 可能适用,暗示 #1 格式不正确?)。
  • 这里实际上有一个函数声明器吗? / 是指向函数的指针,还是包含函数声明符? 我不知道如何在没有 [dcl.fct] 的情况下解析 auto (*g)()auto (*g)() 的形式为 T D1()

标签: c++ language-lawyer auto type-deduction


【解决方案1】:

这里的混淆源于“声明符”的两种不同含义:一种是声明的一部分(在说明符之后),属于一个 entity(或 typedef-name),而另一种是是用于形成前一种的几种句法结构中的任何一种。后一种含义产生了语法产生式ptr-declarator(也包括引用)和noptr-declarator(包括函数和数组)。对于“函数声明器应声明函数”的限制,该含义也是必要的。此外,如果我们采用变量声明

auto (*g)() = /*…*/;

为了 [dcl.spec.auto.general]/3 的目的不涉及“函数声明符”,我们将无法编写

auto (*g)() -> int;

这是普遍接受的(就像问题中的类似示例一样)。

此外,虽然检查“函数声明符是否包含trailing-return-type”的语句不可避免地引用了一个整体 声明符 (这是支持 trailing-return-type 的),它以“声明运算符”的身份这样做,因为它仍然允许嵌套使用此类运算符的上述情况。 (该限制禁止的只是

auto *f() -> int*;

在哪里可以进行扣除,但在这里根本不执行,因为它总是没用的。)

同时,除了实施共识之外,还有一些证据表明,对于更高级别问题的答案是在这些情况下应该允许auto:[dcl.spec.auto.general ]/1 表示函数参数中的 auto 用于声明通用 lambda 或缩写函数模板“如果不是 auto type-specifier 引入 trailing-return- type" 而不是完全不与函数声明符一起使用。

【讨论】:

  • auto *fun(),完整声明符的形式首先匹配 [dcl.ptr] ,将其分解为 * D1 以获得“派生声明符类型列表”,因此"auto D1" 将匹配包含 function declarator 的 [dcl.fct],因此 [dcl.spec.auto.general]/3 将需要包含 function declarator的声明> 声明一个函数。正如声明所做的那样,它是一个函数声明,因此,它遵守规则。这种理解对吗?
  • 相反,对于auto (*ptr)(),完整声明符的形式首先匹配涉及函数声明符的[dcl.fct],而[dcl.fct]将其分解为auto (*ptr)得到“派生声明符类型列表”用于确定声明是什么。最终,包含 function declarator 的声明并没有声明一个函数(它声明了一个指针),所以,它违反了 [dcl.spec.auto.general]/3,对吧?
  • @jackX:当然,“参与”这个词有点松散,但听起来不错。
  • 谢谢。似乎我们将措辞 function declarator 理解为常识中出现在 函数声明 中的完整声明符,正如您所说的“声明符”的第一个含义。我认为有必要澄清 [dcl.spec.auto.general]/3。例如,如果函数声明符包含一个尾随返回类型 ([dcl.fct]),则该尾随返回类型指定函数声明的返回类型。否则,包含函数声明符的声明的完整声明符应声明一个函数。
  • auto *fun(),在我看来,对于这个声明,类型说明符是auto,其完整的声明符是* fun(),应该称为指针声明符[dcl.ptr](I'我不确定,只是类比函数 declarator[dcl.fct])。完整的声明器 *fun() 声明了一个函数,而不是 fun(),对吧?因此,我认为应该澄清 [dcl.spec.auto.general]/3(即“否则,函数声明器应声明函数”)。正如我上面所说的。
猜你喜欢
  • 2012-03-08
  • 2011-06-20
  • 2016-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-08
  • 1970-01-01
相关资源
最近更新 更多