Is a pointer to function (sometimes/always?) a function declarator?
Asked Answered
B

1

4

(This question has been broken out from the discussion to this answer, which highlights CWG 1892)


Some paragraphs of the standard applies specific rules to function declarators; e.g. [dcl.spec.auto]/3 regarding placeholder types [emphasis mine]:

The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is valid. If the function declarator includes a trailing-return-type ([dcl.fct]), that trailing-return-type specifies the declared return type of the function. Otherwise, the function declarator shall declare a function. [...]

restricts where placeholder types may appear with(in) a function declarator. We may study the following example:

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

which both GCC and Clang accepts, deducing g to int(*)().

  • Is a pointer to function (sometimes/always?) a function declarator?
  • Or, alternatively, applied to the example, should #1 be rejected as per [dcl.spec.auto]/3, or does the latter not apply here as a pointer to function is not a function declarator (instead allowing #1 as per [dcl.spec.auto]/4 regarding variable type deduction from initializer)?

The rules for what is a given declarator is not entirely easy to follow, but we may note that, from [dcl.decl]/1

A declarator declares a single variable, function, or type, within a declaration.

that a given declarator is either any of a variable declarator, a function declarator or a type declarator.

  • [dcl.ptr] covers (variable) declarators that are pointers, but does not explicitly (/normatively) mention pointers to functions, albeit does so non-normatively in [dcl.ptr]/4
  • [dcl.fct] covers function declarators but does not mention function pointers as part of function declarations, other than a note that function types are checked during assignment/initialization to function pointers (which is not relevant for what a function declarator is)

My interpretation is that #1 is legal (as per the current standard), as it falls under a variable declarator. If this is actually correct, then the extended question (from the linked thread) is whether

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

is legal or not (/intended to be legal or not as per CWG 1892); as the template parameter arguably contains a declarator that is a function pointer declarator, and not a function declarator.

We may finally note, as similarly pointed out in the linked to answer, that

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

is arguably ill-formed (although this example is also accepted by both GCC and Clang), as the non-type template parameter at #2 is a function declarator and is thus used in an illegal context as per [dcl.spec.auto]/3, as it does not contain a trailing return type and does not declare a function.

Betterment answered 2/2, 2021 at 11:42 Comment(8)
My understanding is that auto (*g)() = f; works for the same reason auto x = 42; works, not because it has something to do with functions. In fact, one can write auto x = 42, (*g)() = f;Raver
@IgorTandetnik For clarity: we are discussing well-formed rather than what works (these are commonly in conflict in language-lawyer questions). I am also leaning towards auto (*g)() = f being well-formed, the key being that there is no function declarator involved in its declaration (such that [dcl.spec.auto]/3 does not apply). Your second example is [dcl.decl]/3 and is not significant here; whether the single declarator being discussed is well-formed or ill-formed (in isolation or as part of a decl. with several declarators).Betterment
there is no function declarator involved in its declaration How auto (*g)() is parsed then?Reign
@LanguageLawyer "leaning towards [...] there is no function declarator" - this is my key question: is there in fact a function declarator involved here? / Is a pointer to function, or does it contain, a function declarator? Please consider posting an answer if you have an argument for "yes it is/does". (In which case [dcl.spec.auto]/3 would arguably apply, implying that #1 is ill-formed?).Betterment
is there in fact a function declarator involved here? / Is a pointer to function, or does it contain, a function declarator? I don't know a way to parse auto (*g)() without [dcl.fct]. auto (*g)() has form T D1().Reign
I think the wording function declarator is vague here.There's no formal definition for function declarator. Does it mean the declarator declares a function or the declarator has the form D1(parameter-declaration-clause )cv-qualifier-seq<opt> ref-qualifier <opt> noexcept-specifier<opt> attribute-specifier-seq<opt>?It appears to me that "A declarator declares a single variable, function, or type, within a declaration." seems to refer to the former. However, auto (*g)() does not declare a function as per functionInvitation
@jackX I agree. Although LanguageLawyer has valid point regarding parsing about, that would arguably cover "the reasonable intent" of declarators in this context, rather than what the actual standard says here: using the term "function declarator" in a strict rule whilst the term itself can arguably be interpreted ambiguously.Betterment
@Betterment We could give an example for contrast, int* fun(), It first matches the form of a pointer, T D where D has the form * attribute-specifier-seq<opt> cv-qualifier-seq <opt> D1 , Can it be called pointer declarator? I don't think. Instead, the declarator indeed declares a function here.Invitation
C
1

The confusion here arises from two different meanings of "declarator": one is the portion of a declaration (after the specifiers) that pertains to one entity (or typedef-name), while the other is any of the several syntactic constructs used to form the former kind. The latter meaning gives rise to the grammar productions ptr-declarator (which also covers references) and noptr-declarator (which includes functions and arrays). That meaning is also necessary to give any meaning to a restriction that a "function declarator shall declare a function". Moreover, if we took the variable declaration

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

to not involve a "function declarator" for the purposes of [dcl.spec.auto.general]/3, we would not be able to write

auto (*g)() -> int;

which is universally accepted (just as is the similar example in the question).

Moreover, while the statement that checks whether "the function declarator includes a trailing-return-type" inevitably refers to an overall declarator (which is what supports a trailing-return-type), it does so in its capacity as a "declaration operator" because it still allows the above cases with nested use of such operators. (What that limitation forbids is just

auto *f() -> int*;

where deduction would work but isn't performed at all here because it would always be useless.)

Meanwhile, there is some evidence, beyond implementation consensus, that the answer to the higher-level question is that auto in these cases should be allowed: [dcl.spec.auto.general]/1 says that auto in a function parameter serves to declare a generic lambda or abbreviated function template "if it is not the auto type-specifier introducing a trailing-return-type" rather than if it is not used with a function declarator at all.

Canonicals answered 7/2, 2021 at 17:28 Comment(5)
Such as auto *fun(), the form of the complete declarator first matches [dcl.ptr] , which will decomposes it to * D1 for getting the "derived-declarator-type-list", hence "auto D1" will matche [dcl.fct] which involve function declarator, Hence [dcl.spec.auto.general]/3 will require the declaration that include the function declarator to declare a function. As the declaration do, it's a function declaration, So, it obeys the rule. Is this understanding Right?Invitation
Instead, for auto (*ptr)(), the form of the complete declarator first matches [dcl.fct] which involves the function declarator, however [dcl.fct] will decompose it to auto (*ptr)to get "derived-declarator-type-list" for determining what the declaration is. Eventually, the declaration that include the function declarator does not declare a function(it declares a pointer), So, it violates [dcl.spec.auto.general]/3, Right?Invitation
@jackX: The term “involve” is a bit loose, of course, but that sounds right.Canonicals
Thanks. It seems that we understand the wording function declarator as the complete declarator that appears in a function declaration in common sense, as you said for the first meaning of "declarator". I think it's necessary to clarify [dcl.spec.auto.general]/3. Such as, If the function declarator includes a trailing-return-type ([dcl.fct]), that trailing-return-type specifies the declared return type of the function. Otherwise, the complete declarator of a declaration that comprises function declarators shall declare a function.Invitation
auto *fun(), In my mind, for this declaration, the type specifier is auto and its complete declarator is * fun(), which shall be called pointer declarator [dcl.ptr](I'm not sure, just analogy from function declarator[dcl.fct]). The complete declarator *fun() declares a function rather than fun() does, Right? Hence, I think [dcl.spec.auto.general]/3(namely, "Otherwise, the function declarator shall declare a function") should be clarified. as I said above.Invitation

© 2022 - 2024 — McMap. All rights reserved.