Unified Function Syntax

ISO/IEC JTC1 SC22 WG21 N2890 = 09-0080 - 2009-06-21

Lawrence Crowl, crowl@google.com, Lawrence@Crowl.org
Alisdair Meredith, public@alisdairm.net

This paper is a revision of N2825.

Introduction
New Function Declaration Syntax
The Type of Empty–Lambda-Capture Lambdas
New Type-ID and Parameter Syntax
Local Functions
    Overloading Local Functions
    Forwarding Local Functions
Named Lambdas
    Overloading Named Lambdas
    Forwarding Named Lambdas
Auto with Function Definitions
Other Issues
Proposed Wording
    3.3.2 Point of declaration [basic.scope.pdecl]
    3.3.3 Local scope [basic.scope.local]
    3.5 Program and linkage [basic.link]
    5.1.2 Lambda expressions [expr.prim.lambda]
    7 Declarations [dcl.dcl]
    7.1.6.4 auto specifier [dcl.spec.auto]
    8 Declarators [dcl.decl]
    8.1 Type names [dcl.name]
    8.3.5 Functions [dcl.fct]
    8.4 Function definitions [dcl.fct.def]
    13 Overloading [over]

Introduction

The sytax for both the new function declarator syntax (N2541 New Function Declarator Syntax Wording) and lambda expressions (N2550 Lambda Expressions and Closures: Wording for Monomorphic Lambdas (Revision 4)) are similar. As suggested by Alisdair Meredith (N2511 Named Lambdas and Local Functions), the syntax for both could be made more similar, thus simplifying the view of the programmer. The British position (N2510 BSI Position on Lambda Functions), U.K. comments, and U.S. comments support this work.

Such a unification would address the concerns of Daveed Vandevoorde (N2337 The Syntax of auto Declarations) that the auto keyword was too overloaded in its use for both a new function declarator syntax and for automatically deducing variable type (N1984 Deducing the type of variable from its initializer expression (revision 4)).

This paper proposes the syntactic unification of function declarations and lambda expressions. The key technical insight enabling this unification is that an empty lambda capture means that no local environment is captured, which is exactly the semantics of a function. That is, a function is a special case of a lambda.

The paper takes the new lambda syntax (N2550) as the starting point for syntactic unifications, and specific syntactic suggestions in N2511 no longer apply. As a simplistic unification would introduce unfortunate irregularities in semantics, we also propose regularizing the semantics of such declarations.

This paper presents a unification that includes more capability than in the earliest papers. This change in approach addresses a concern that earlier proposals did not go far enough to justify any support. Rather than prematurely select those parts of the end state are acceptable for C++0x, we choose to propose the full end state and let the committee decide if and how to scale back the proposal. However, we believe that everything within this proposal is both desirable and implementable within C++0x given the existing presence of lambda expressions. We have also incorporated straightforward comments from the core working group meeting in during the March 2009 ISO C++ meeting. Furthermore, we expose some of the more substantive suggestions from core so that the receive a broader exposure before the committee commits to them.

New Function Declaration Syntax

Based on direction from the September 2008 ISO C++ meeting, our general approach is to replace function declarations using an auto type specifier and a "->" return type (N2541) with new declaration syntax for functions.

The syntax for function declarations extends the syntax for lambda expressions with declaration specifiers and an id expression to name the function. A lambda expression is distinct from a function by the presence of an id expression.

lambda-expression:
lambda-introducer function-headingopt compound-statement
function-declaration:
decl-specifier-seqopt lambda-introducer id-expression function-heading

[Note: Clark Nelson suggested using function-introducer rather than lambda-introducer.]

For functions at namespace scope, the lambda-introducer of the form [] is semantically correct. For functions at class scope, the lambda-introducer of the form [] is semantically correct, with the understanding that class-scope non-static functions still have an implicit this parameter. (That is, this is still passed, not captured.)


int x=0, y=0;
[] f(int z)->int { return x+y+z; }
struct s {
    int k;
    [] g(int z)->int;
};
[] s::g(int z)->int { return k+x+z; }

In the process of unifying the syntax, we refactored and unified the grammar. There are, however, some context-dependent textual restrictions on the use of that grammar.

The Type of Empty–Lambda-Capture Lambdas

The semantics of a lambda with an empty capture list and a function are nearly uniform. This uniformity was noted in U.K. national body comment 226. Exposing this uniformity would allow programmers to use lambdas and exploit existing function-based interfaces.

Problem: A lambda with an empty capture list has identical semantics to a regular function type. By requiring this mapping we get an efficient lambda type with a known API that is also compatible with existing operating system and C library functions.

Resolution: Add a new paragraph: "A lambda expression with an empty capture set shall be convertible to pointer to function type R(P), where R is the return type and P is the parameter-type-list of the lambda expression." Additionally it might be good to (a) allow conversion to function reference and (b) allow extern "C" function pointer types.

There are two approaches to expressing this uniformity.

  1. Provide an implicit conversion operator from the closure object to a function pointer.

  2. Have a lambda with an empty lambda capture name a function rather than return a closure object.

These two approaches have different effects on the programmer model.

  1. All function-local uses of [....] produce a lambda object. Only non-local uses of [] may appear as template non-type arguments. Only non-local uses of [] are affected by argument-dependent lookup.

  2. All uses of [] name a function. All such uses may appear as template arguments of function type. All uses of [], but none with a non-empty lambda-capture, are affected by argument-dependent lookup.

They also have different effects on program optimization.

  1. The closure object retains a unique type, which means that when the lambda appears as a function argument to a standard template algorithm, the corresponding algorithm instance will be unique to the lambda. This specificity enables inlining in the compiler front end, which in turn enables a very specialized algorithm.

  2. The lambda will decay to a function pointer when a function argument to standard template algorithm. While the same specialized algorithm above is still possible, it requires inter-procedural constant-value propogation, which is typically done only in the compiler back ends at higher levels of optimization. On the other hand, the number of template instances is much reduced, and so when specializing the algorithm is not profitable, the code size will be smaller.

New Type-ID and Parameter Syntax

The auto-> syntax was usable in type-ids and parameters, and so the equivalent new syntax must be added.

type-id:
type-specifier-seq attribute-specifieropt abstract-declaratoropt
lambda-introducer function-heading
parameter-declaration:
decl-specifier-seq attribute-specifieropt declarator parameter-defaultopt
decl-specifier-seq attribute-specifieropt abstract-declaratoropt parameter-defaultopt
lambda-introducer declarator-idopt function-heading parameter-defaultopt

The primary likely concern with this syntax of type-ids is ambiguity with lambda expressions. These are unambiguously resolved by the presence or absence of a following compound statement. While the common prefix is non-trivial, it shares the same grammar rules and provides the same information. That is, the parse can proceed without backtracking.

A secondary likely concern with this syntax is that it cannot currently represent all declarations. While this limitation was understood by the authors in prior versions, it was not obvious, and Clark Nelson noticed.

There doesn't seem to be any way to rewrite this declaration:


int (*fpfi())();

using exclusively postfix return specifications. It would look something like:


[]fpfi()->[]()->int;

Of course this omits the pointer-declarator, but the grammar doesn't seem to provide any place for it. Worse, my intuition doesn't suggest to me exactly where it ought to go. Worse still, the principles on which the grammar seems to be based really don't allow for it anywhere.

In fact, the intent was to enable just such a declaration. The mechanism to enable such declarations is the implicit function to pointer-to-function conversion of parameter types. Unfortunately, previous revisions of the paper failed to modify the appropriate text. That failure has been corrected.

Even so, previous revisions of the paper did permit a simlar declaration,


[]fpfi()->int (*)();

because the return type is a type-id, which includes abstract declarators.

However, the proposed syntax still does not permit full declarations. In particular, the syntax does not permit declaring a pointer to a pointer to a function. An intermediate typedef is necessary. Such intermediate typedefs are also required when mixing extern "C" and extern "C++" function types in the same declaration.

Local Functions

The new function syntax enables defining functions at local scope. Local functions can improve program clarity by not affecting containing scopes and by keeping the definition and use of functions closer together.


bool halvable(int n) {
    [] even(unsigned n)->bool { return n & 0 ? true : false; }
    return even(n);
}

Note that we preserve the existing non-local interpretation of the existing local function declarations.


void halvable(int n) {
    int even(int n); // implicit extern declaration
    return even(n);
}

Due to concerns about the effects of argument-dependent lookup, the core commitee recommends that no operator names be permitted on local functions.

Overloading Local Functions

The syntax naturally permits overloading of local functions.


double a(int n) {
    const double pi = 3.14159;
    [] p(int m) { return m*pi; }
    [] p(double m) { return m*pi; }
    return p(3.0);
}

However, because of concern over interoperability with named lambdas, the core committee has a preference for not permitting overloading of local functions at this time. Relaxing that restriction in the future would likely be upwards compatible.

Forwarding Local Functions

The new syntax also enables the forward declaration of local functions. For example, and excusing the algorithm,


bool halvable(int n) {
    [] even(unsigned n)->bool;
    [] odd(unsigned n)->bool { return n == 0 ? false : even(n-1); }
    [] even(unsigned n)->bool { return n == 0 ? true : odd(n-1); }
    return even(n);
}

However, because of concern over interoperability with named lambdas, the core committee has a preference for not permitting forwarding of local functions at this time. Relaxing that restriction in the future would likely be upwards compatible.

Named Lambdas

Local functions with non-empty lambda captures are simply named lambdas.


int h(int b) {
    [] m(int z)->int // local function
        { return x+z; } // b is not in scope
    [&] n(int z)->int // named lambda
        { return b+x+z; } // b is in scope, by reference
    return m(b) + n(b);
}

Named lambdas provide a way to use a lambda at multiple places, though this could be done with a variable holding the closure object instead.


int h(int b) {
    auto m = [&](int z)->int { return b+x+z; };
    return m(b) + m(b);
}

Due to concerns about the effects of argument-dependent lookup, the core commitee recommends that no operator names be permitted on named lambdas.

Named lambdas provide two advantages over this variable approach. First, they can enter into overload sets. Second, they can enable forward declaration.

Overloading Named Lambdas

Overloading named lambdas is conceptually as simple as overloading local functions. However, the implementation is more intrusive on the compiler because it must now keep add named lambdas into overload sets. Because of this difficulty, the core committee has a preference for not permitting overloading of named lambdas at this time. Relaxing that restriction in the future would likely be upwards compatible.

Forwarding Named Lambdas

Using a forward-declared named lambda require that the implementation know the type of the closure object, which in turn requires that the compiler know the effective capture set. While it is possible to determine the effective capture set at the point of named lambda definition, such an approach would require considerable compiler effort. A more pragmatic approach requires determining the effective capture list at the point of declaration. This determination can be done in one of two ways.

  1. Require that all captured variables be explicitly listed in the declaration, default captures would not be allowed.

  2. As above, except that a default capture of "&" (by-reference) is permitted. The practical implication is that references are captured via a frame pointer rather by a set of references to individual variables. While the frame-pointer implementation is clearly superior, the committee should make an explicit decision to require it.

Whatever choices are made above, there is still the issue of redundancy and consistency in the specification of the capture set between declaration and definition. To minimize confusion on the part of programmers, this proposal requires that the lambda-capture be identical between declaration and definition. This requirement comes at the cost of some redundancy in declarations.

In addition to knowing the effective capture set, the compiler must capture that set. The capture can be done either at the first declaration or at the definition. Capturing at the elaboration of the definition seems to provide the greatest programmer flexibility, and is the basis for this proposal. So, named lambdas must not be invoked or copied before the elaboration of their definition. As a consequence, mutually recursive named lambdas must capture the other by reference, which in turn constrains the representation of reference captures as discussed above.


int a(int n) {
    [&,n] p(int m)->int;
    [&,n] q(int m) { return p(m-1)+n; }
    // okay, use of p in a lambda body is not yet invoked
    int x = p(3);
    // error, the definition of p has not been elaborated
    [&,n] p(int m)->int { return m>0 ? q(m-1)+n : 0; }
    int y = p(4);
    // okay, p has been defined
}

Because of the definitional complexities above, the core committee has a preference for not permitting forward declaration of named lambdas at this time. Relaxing that restriction in the future would likely be upwards compatible.

Auto with Function Definitions

Since the auto keyword is not longer used to specify late return types, there is a choice in whether or not it applies to function definitions. The simplest approach is to reserve it strictly to object definitions. However, the "infer from initializer" interpretation can also permit infering a return type of a function definition.


auto twice(int x) { return x+x; }

As with lambdas and constexpr functions, we require that the body consist only of a return statement.

Likewise, one can infer the return type of function defintions using auto within the new syntax.


[] twice(int x) -> auto { return x+x; }

That is, the auto specifier is syntactic sugar for decltype(expr), where expr is the expression in the return statement.

Other Issues

There is some concern about whether the appropriate wording for a closure is closure object, closure value, or simply closure.

Proposed Wording

The proposed wording shows changes from working draft N2857. Note that in particular, it does not reflect the wholesale changes to lambda wording proposed in N2859 New wording for C++0x Lambdas, Daveed Vandevoorde, 2009-03-19.

3.3.2 Point of declaration [basic.scope.pdecl]

Edit paragraph 9 as follows. The intent is to enable local functions while preserving existing semantics for existing code. This edit should remain even if local functions are not adopted now, so as to preserve future possible standardization.

[Note: friend declarations refer to functions or classes that are members of the nearest enclosing namespace, but they do not introduce new names into that namespace (7.3.1.2). Function declarations via a simple-declaration at block scope and function or object declarations with the extern specifier at block scope refer to delarations that are members of an enclosing namespace, but they do not introduce new names into that scope (3.5 [basic.link]). —end note]

3.3.3 Local scope [basic.scope.local]

Edit paragraph 2 as follows. Note the added comma in the second sentence. This edit simply adjusts to grammar simplification.

The potential scope of a function parameter name (including one appearing in a lambda-parameter-declaration-clause parameter-declaration-clause) or of a function-local predefined variable in a function definition (8.4) begins at its point of declaration. If the function has a function-try-block, the potential scope of a parameter or of a function-local predefined variable ends at the end of the last associated handler, otherwise it ends at the end of the outermost block of the function definition. A parameter name shall not be redeclared in the outermost block of the function definition nor in the outermost block of any handler associated with a function-try-block.

3.5 Program and linkage [basic.link]

Edit within paragraph 6 as follows. The intent is to enable local functions while preserving existing semantics for existing code. This edit should remain even if local functions are not adopted now, so as to preserve future possible standardization.

The name of a function declared in block scope via a simple-declaration [Footnote: See [dcl.fct] for function declarations not via simple-declaration. —end footnote], and the name of a function or an object declared by a block scope extern declaration, have linkage. If there is a visible declaration of an entity with linkage having the same name and type, ignoring entities declared outside the innermost enclosing namespace scope, the block scope declaration declares that same entity and receives the linkage of the previous declaration. If there is more than one such matching entity, the program is ill-formed. Otherwise, if no matching entity is found, the block scope entity receives external linkage.

Note that if local function definitions are not permitted, the following sentence needs to be added to this paragraph.

If a declared function has no linkage, the program is ill-formed.

5.1.2 Lambda expressions [expr.prim.lambda]

Edit the syntax as follows. This edit refactors the lambda syntax to reuse the function syntax, which includes modifications to support lambda.

lambda-expression:
lambda-introducer lambda-parameter-declarationopt compound-statement
lambda-introducer function-headingopt compound-statement
lambda-introducer:
[ lambda-captureopt ]
lambda-capture:
capture-default
capture-list
capture-default , capture-list
capture-default:
&
=
capture-list:
capture
capture-list , capture
capture:
identifier
& identifier
this
lambda-parameter-declaration:
( lambda-parameter-declaration-listopt ) mutableopt attribute-specifieropt exception-specificationopt lambda-return-type-clauseopt
lambda-parameter-declaration-list:
lambda-parameter
lambda-parameter , lambda-parameter
lambda-parameter:
decl-specifier-seq attribute-specifieropt declarator
lambda-return-type-clause:
-> attribute-specifieropt type-id

Edit paragraph 1 as follows. The intent of this edit is to follow the unification of the grammar, and then add restrictions to remain consistent with the existing lambda syntax.

In the function-heading ([dcl.fct]) of a lambda-expression: In a lambda-parameter-declaration in the function-qualifiers, the attribute-specifier appertains to the lambda. In a lambda-return-type-clause; in the return-type-clause, the attribute appertains to the lambda return type. type; the mutable qualifier shall occur only when there is no lambda-capture; there shall be no cv-qualifier-seq or ref-qualifier; and each parameter shall have a declarator-id.

Edit paragraph 7 as follows. The intent of this edit is make an empty lambda-capture indicate a function, which is option 2 of The Type of Empty–Lambda-Capture Lambdas.

If the effective capture set is empty, the lambda-expression names an anonymous function of type R(P), where R is the return type and P is the parameter-type-list of the lambda expression. The corresponding function pointer [conv.func] is the closure object. The Otherwise, the type of the closure object is a class with a unique name, call it F, considered to be defined at the point where the lambda expression occurs.

Edit paragraph 10 as follows. Note the insertion of a comma. The intent of this edit is to follow the changes to the grammar.

7 Declarations [dcl.dcl]

Edit paragraph 1 as follows. The intent of this edit is to enable function-local function declarations and definitions.

....
declaration:
block-declaration
function-definition
template-declaration
explicit-instantiation
explicit-specialization
linkage-specification
namespace-definition
empty-definition
concept-definition
concept-map-definition
attribute-declaration
block-declaration:
simple-declaration
function-declaration ;
function-definition
asm-definition
namespace-alias-definition
using-declaration
using-directive
static_assert-declaration
alias-declaration
opaque-enum-declaration
....

7.1.6.4 auto specifier [dcl.spec.auto]

Remove paragraph 1. The intent of this edit is to remove the auto-> syntax of N2541 New Function Declarator Syntax Wording.

The auto type-specifier signifies that the type of an object being declared shall be deduced from its initializer or specified explicitly at the end of a function declarator.

Edit paragraph 2 as follows. The intent of this edit is to remove the auto-> syntax and to enable auto with function definitions. If that latter feature is not desired, this paragraph would be removed. In that event, the lead-in to paragraph 3 also changes.

The auto type-specifier may appear with a function declarator with a late-specified return type (8.3.5) in any context where such a declarator is valid, and the use of auto is replaced by the type specified at the end of the declarator. definition ([dcl.fct.def])

Edit within paragraph 3 as follows. The intent of the edit is ensure flow of text from the above.

Otherwise, the type of the object is auto type-specifier signifies that the type of an object being declared shall be deduced from its initializer. The name of the object being declared shall not appear in the initializer expression. The auto type-specifier is allowed when declaring objects in a block (6.3), in namespace scope (3.3.5), and in a for-init-statement (6.5.3). The decl-specifier-seq shall be followed by one or more init-declarators, each of which shall have a non-empty initializer of either of the following forms:

= assignment-expression
( assignment-expression )

8 Declarators [dcl.decl]

Edit paragraph 4 as follows. The intent of the edit is to remove the auto-> syntax. In the process, the ptr-declarator rule becomes redundant and is eliminated. Furthermore, the edit refactors the grammar.

Declarators have the syntax

declarator:
ptr-declarator
noptr-declarator parameters-and-qualifiers -> attribute-specifieropt type-id
ptr-declarator:
noptr-declarator
ptr-operator ptr-declarator
noptr-declarator:
declarator-id attribute-specifieropt
noptr-declarator parameters-and-qualifiers
noptr-declarator [ constant-expressionopt ] attribute-specifieropt
( ptr-declarator )
parameters-and-qualifiers:
( parameter-declaration-clause ) attribute-specifieropt cv-qualifier-seqopt ref-qualifieropt exception-specificationopt
( parameter-declaration-clause ) function-qualifiers
ptr-operator:
* attribute-specifieropt cv-qualifier-seqopt
&
&&
ref-qualifier
::opt nested-name-specifier * attribute-specifieropt cv-qualifier-seqopt
cv-qualifier-seq:
cv-qualifier cv-qualifier-seqopt
cv-qualifier:
const
volatile
ref-qualifier:
&
&&
declarator-id:
...opt id-expression
::opt nested-name-specifieropt class-name

A class-name has special meaning in a declaration of the class of that name and when qualified by that name using the scope resolution operator :: (5.1, 12.1, 12.4).

8.1 Type names [dcl.name]

Edit within paragraph 1 as follows. The intent of this edit is to remove the auto-> syntax and add the new function syntax.

type-id:
type-specifier-seq attribute-specifieropt abstract-declaratoropt
lambda-introducer function-heading
abstract-declarator:
ptr-abstract-declarator
noptr-abstract-declaratoropt parameters-and-qualifiers -> attribute-specifieropt type-id
...
ptr-abstract-declarator:
noptr-abstract-declarator
ptr-operator ptr-abstract-declaratoropt
noptr-abstract-declarator:
noptr-abstract-declaratoropt parameters-and-qualifiers
noptr-abstract-declaratoropt [ constant-expression ] attribute-specifieropt
( ptr-abstract-declarator )

8.3.5 Functions [dcl.fct]

Edit paragraph 1 as follows. The intent of this edit is to follow the grammar refactoring.

In a declaration T D where D has the form

D1 ( parameter-declaration-clause ) attribute-specifieropt cv-qualifier-seqopt ref-qualifieropt exception-specificationopt
D1 parameters-and-qualifiers

and the type of the contained declarator-id in the declaration T D1 is "derived-declarator-type-list T", the type of the declarator-id in D is "derived-declarator-type-list function of ( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifieropt parameters-and-qualifiers returning T". The optional attribute-specifier of the function-qualifiers of the parameters-and-qualifiers appertains to the function type.

Delete paragraph 2. The intent of this edit is to remove the auto-> syntax.

In a declaration T D where D has the form

D1 ( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifieropt exception-specificationopt -> type-id

and the type of the contained declarator-id in the declaration T D1 is "derived-declarator-type-list T," T shall be the single type-specifier auto and the derived-declarator-type-list shall be empty. Then the type of the declarator-id in D is "function of (parameter-declaration-clause) cv-qualifier-seqopt ref-qualifieropt returning type-id". Such a function type has a late-specified return type.

Delete paragraph 3. The intent of this edit is to remove the auto-> syntax.

The type-id in this form includes the longest possible sequence of abstract-declarators. [Note: This resolves the ambiguous binding of array and function declarators. [Example:

auto f()->int(*)[4]; // function returning a pointer to array[4] of int
// not function returning array[4] of pointer to int

end example] —end note]

Insert a new paragraph 2. The intent of this edit is to add the new function syntax. Note that the position of the mutable keyword seems out of place. It is consistent with the existing grammar, but a more self-consistent grammar would place the mutable keyword after the attribute-specifier.

Function declarations with late-specified return type have the form:

function-declaration:
decl-specifier-seqopt lambda-introducer id-expression function-heading
function-heading:
parameter-and-qualifiers return-type-clauseopt
function-qualifiers:
mutableopt attribute-specifieropt cv-qualifier-seqopt ref-qualifieropt exception-specificationopt
return-type-clause:
-> attribute-specifieropt type-id

[Example:


int x=0, y=0;
[] f(int z)->int { return x+y+z; }
struct s {
    int k;
    [] g(int z)->int;
};
[] s::g(int z)->int { return k+x+z; }

end example]

Insert a new paragraph after the above paragraph. The intent is to restrict use of decl-specifiers; to restrict the use of mutable in function-qualifiers; to prohibit operators; and to prohibit forwarding.

The decl-specifier-seq of a function-declaration may contain only those that apply to functions: static, extern, friend, typedef, constexpr, and function-specifier. The function-qualifiers shal contain mutable only when part of a function-declaration or lambda-expression with a non-empty lambda-capture. The id-expression of a function-declaration may contain neither a operator-function-id nor a conversion-function-id. A non-extern local function-declaration shall be part of a function-definition. [Footnote: This restriction prohibits forward declarations. —end footnote]

Insert a new paragraph after the above paragraph. The intent of this edit is to define the semantics of a missing return-type-clause.

Within a function-heading, a missing return-type-clause indicates that the return type is void, unless the heading is part of a lambda or function definition with a compound-statement of the form { return expression ; }, in which case the return type is the type of expression. [Note: See also (5.1.1 [expr.prim.lambda]). —end note] [Example:


[] p(int m); // return type is void
[] q(int m) { return m; } // return type is int
[] r(double m) { p(m); } // return type is void

end example]

Insert a new paragraph after the above paragraph. The intent of this edit is to reinforce the local functions implicit in the grammar change.

Functions may be local to another function. [Example:


[] halvable(int k)->bool; {
    [] even(unsigned n)->bool { return (n & 1) == 0; }
    return even(k);
}

end example]

Insert a new paragraph after the above paragraph. The intent of this edit is to tie the descriptions of lambda and function together. Furthermore, we require capture lists not in local scope to reflect the fact that they have nothing to capture.

The semantics of a non-empty lambda-capture are described in ([expr.prim.lambda]). A non-empty lambda-capture shall only appear in block scope, but may not appear in extern declarations. These definitions are named lambdas.

If forward declarations are permitted, then add the following paragraph after the above paragraph.

The lambda-capture for a named lambda declaration and its definition must be identical. The point-of-capture for a named lambda is its definition. The invocation of a named lambda before the elaboration of its definition is an error, no diagnostic required. [Example:


int a(int n) {
    [&,n] p(int m)->int;
    [&,n] q(int m) { return p(m-1)+n; }
    // okay, use of p in a lambda body is not yet invoked
    int x = p(3);
    // error, the definition of p has not been elaborated
    [&,n] p(int m)->int { return m>0 ? q(m-1)+n : 0; }
    int y = p(4);
    // okay, p has been defined
}

end example]

Edit the existing paragraph 4 as follows. The intent of this edit is to add the new function syntax for parameters. In the process, we refactor the grammar for default parameters.

A type of either form is a function type. [Footnote: As indicated by syntax, cv-qualifiers are a signficant component in function return types. —end footnote]

parameter-declaration-clause:
parameter-declaration-listopt ...opt
parameter-declaration-list , ...
parameter-declaration-list:
parameter-declaration
parameter-declaration-list , parameter-declaration
parameter-declaration:
decl-specifier-seq attribute-specifieropt declarator parameter-defaultopt
decl-specifier-seq attribute-specifieropt declarator = assignment-expression
decl-specifier-seq attribute-specifieropt abstract-declaratoropt parameter-defaultopt
decl-specifier-seq attribute-specifieropt abstract-declaratoropt = assignment-expression
lambda-introducer declarator-idopt function-heading parameter-defaultopt
parameter-default:
= assignment-expression

Within the existing paragraph 6, edit as follows.

.... After determining the type of each parameter, any parameter of type "array of T" or "function returning T" is adjusted to be "pointer to T" or "pointer to function returning T," respectively. After determining the return type, a return type of "array of T" or "function returning T" is adjusted to be "pointer to T" or "pointer to function returning T," respectively. ....

Edit within paragraph 12 as follows. The intent of this edit is to make the examples follow the new syntax.

[Note: typedefs and late-specified return types are sometimes convenient when the return type of a function is complex. For example, the function fpif above could have been declared


typedef int IFUNC(int);
IFUNC* fpif(int);

or


auto [] fpif(int)->int(*)(int)

or, exploiting implicit function-to-pointer conversion in return types,


[] fpif(int)->[](int)->int

A late-specified return type is most useful for a type that would be more complicated to specify before the declarator-id:


template <class T, class U> auto [] add(T t, U u) -> decltype(t + u);

rather than


template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u);

end note]

8.4 Function definitions [dcl.fct.def]

Edit paragraph 1 as follows. The intent of the edit is to add the new function syntax. In the process, we refactor the grammar.

Function definitions have the form

function-definition:
decl-specifier-seqopt attribute-specifieropt declarator function-body
decl-specifier-seqopt attribute-specifieropt declarator = default ;
decl-specifier-seqopt attribute-specifieropt declarator = delete ;
function-declaration function-body
function-body:
ctor-initializeropt compound-statement
function-try-block
= default ;
= delete ;

After paragraph 8, insert a new paragraph. The intent of this edit is to allow auto in return types for function definitions.

The simple-type-specifier of the return type of a function or lambda definition may contain the auto type specifier, provided that the form of the compound-statement is { return expr; } and the return type is determined from the type of the expression using the rules for template argument deduction, as in (7.1.6.4 [dcl.spec.auto]). [Example:


[] m(int a) -> auto { return a; }
auto m(int a) { return a; }

end example]

Within paragraph 9, edit as follows.

A function definition of the form forms:

decl-specifier-seqopt attribute-specifieropt declarator = default ; function-declaration = default ;

is called an explicitly-defaulted definition. ...

Within paragraph 10, edit as follows.

A function definition of the form forms:

decl-specifier-seqopt attribute-specifieropt declarator = delete ; function-declaration = delete ;

is called a deleted definition. ...

13 Overloading [over]

Edit paragraph 1 as follows. The intent is to prohibit overloading of local functions and named lambdas.

When two or more different declarations are specified for a single name in the same scope, that name is said to be overloaded. By extension, two declarations in the same scope that declare the same name but with different types are called overloaded declarations. Only function declarations can be overloaded; object and type declarations cannot be overloaded. Futhermore, non-extern local function and named lambdas shall not be overloaded. [Example:

double a(int n) {
    [n] p(int m) { return m+n; }
    [n] p(double m) { return m+n; } // error: local overload
    return p(3.0);
}

end example]

Alternatively, edit paragraph 1 as follows. The intent is to clarify the permissible overloading of local functions and named lambdas.

When two or more different declarations are specified for a single name in the same scope, that name is said to be overloaded. By extension, two declarations in the same scope that declare the same name but with different types are called overloaded declarations. Only function and named lambda declarations can be overloaded; object and type declarations cannot be overloaded. [Example:


double a(int n) {
    [n] p(int m) { return m+n; }
    [n] p(double m) { return m+n; }
    return p(3.0);
}

end example]