ISO/IEC JTC1 SC22 WG21 N2825 = 09-0015 - 2009-02-08
Lawrence Crowl, crowl@google.com, Lawrence@Crowl.org
Alisdair Meredith, public@alisdairm.net
This paper is a substantial revision of N2763.
auto
specifier [dcl.spec.auto]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) supports 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 list 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 prior 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.
Based on direction from the September 2008 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.
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 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.
To ensure that lambdas and functions have uniform semantics where possible, we change a lambda with an empty capture list to name a function rather than return a closure object. Naming a function rather than returning a function pointer is important to template specialization when the lambda is a template argument. The implicit conversion to function pointer also enables use of a lambda expression in existing interfaces requiring function pointers.
The auto->
syntax was usable in type-ids and parameters,
and so the equivalent new syntax must be added.
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.
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; [] odd(unsigned n)->int { return n == 0 ? false : even(n-1); } [] even(unsigned n)->bool { return n == 0 ? true : odd(n-1); } 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); }
Local functions with non-empty capture lists 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); }
Named lambdas provide two advantages over this variable approach. First, they can enter into overload sets.
double a(int n) { [n] p(int m) { return m+n; } [n] p(double m) { return m+n; } return p(3.0); }
Second, they can enable forward declaration. The point of capture is at the elaboration of the definition, not the declaration, 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.
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 }
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, 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; }
The proposed wording shows changes from working draft N2798.
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 theextern
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]
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 appearingin alambda-parameter-declaration-clauseparameter-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.
Edit 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.
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
&
identifierthis
lambda-parameter-declaration:(
lambda-parameter-declaration-listopt) mutable
opt attribute-specifieropt exception-specificationopt lambda-return-type-clauseoptlambda-parameter-declaration-list:lambda-parameterlambda-parameter,
lambda-parameterlambda-parameter:decl-specifier-seq attribute-specifieropt declaratorlambda-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
a lambda-parameter-declarationthe function-qualifiers of a function-heading ([dcl.fct]) the attribute-specifier appertains to the lambda. In alambda-return-type-clausereturn-type-clause the attribute appertains to the lambda return type. Themutable
qualifier shall occur only when the lambda-introducer is not empty. The cv-qualifier-seq or ref-qualifier shall not be present. Each parameter in the function-heading shall have a declarator-id.
Edit paragraph 7 as follows. The intent of this edit is make empty–capture-list lambdas name functions.
If the effective capture set is empty, the lambda-expression names an anonymous function of type
R(P)
, whereR
is the return type andP
is the parameter-type-list of the lambda expression. The corresponding function pointer [conv.func] is the closure object.TheOtherwise, the type of the closure object is a class with a unique name, call itF
, 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.
- ....
- The return type is the type denoted by the type-id in the
lambda-return-type-clausereturn-type-clause; for a lambda expression that does not contain alambda-return-type-clausereturn-type-clause, the return type isvoid
, unless the compound-statement is of the form{ return
expression; }
, in which case the return type is the type of expression. [Note: See also (8.3.5 [dcl.fct]). —end note]- ....
Edit paragraph 12 as follows. The intent of this edit is to follow the changes to the grammar.
If every name in the effective capture set is preceded by
&
and the lambda expression is notmutable
,F
is publicly derived fromstd::reference_closure<R(P)>
(20.6.18), whereR
is the return type andP
is theparameter-type-listparameter-declaration-clause of the lambda expression. Converting an object of typeF
to typestd::reference_closure<R(P)>
and invoking its function call operator shall have the same effect as invoking the function call operator ofF
. [Note: This requirement effectively means that suchF
's must be implemented using a pair of a function pointer and a static scope pointer. —end note]
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
- 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
- ....
Edit paragraph 1 as follows. The intent of this edit is provide a grammar subroutine for function declarations.
The specifiers that can be used in a declaration are
- decl-specifier:
- storage-class-specifier
- type-specifier
function-specifierfriend
typedef
constexpr
- alignment-specifier
- func-decl-specifier
- decl-specifier-seq:
- decl-specifier-seqopt decl-specifier
- func-decl-specifier:
- func-storage-class-specifier
- function-specifier
friend
typedef
constexpr
- func-decl-specifier-seq:
- func-decl-specifier-seqopt func-decl-specifier
Edit paragraph 1 as follows. The intent of this edit is provide a grammar subroutine for function declarations.
The storage class specifiers are
- storage-class-specifier:
register
static
thread_local
extern
mutable
- func-storage-class-specifier:
- func-storage-class-specifier:
static
extern
....
auto
specifier [dcl.spec.auto]
Remove paragraph 1.
The intent of this edit is to remove the auto->
syntax.
Theauto
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.
The
auto
type-specifier may appear with a functiondeclarator with a late-specified return type (8.3.5) in any context where such a declarator is valid, and the use ofdefinition ([dcl.fct.def])auto
is replaced by the type specified at the end of the declarator.
Edit paragraph 3 as follows. The intent of the edit is ensure flow of text from the above.
Otherwise, the
type of the object isauto
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)
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-declaratornoptr-declarator parameters-and-qualifiers->
attribute-specifieropt type-idptr-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-nameA 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).
Edit 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)
Edit paragraph 1 as follows. The intent of this edit is to follow the grammar refactoring.
In a declaration
T D
whereD
has the form
D1 (
parameter-declaration-clause)
attribute-specifieropt cv-qualifier-seqopt ref-qualifieropt exception-specificationopt
D1 (
parameter-declaration-clause)
function-qualifiersand the type of the contained declarator-id in the declaration
T D1
is "derived-declarator-type-list T", the type of the declarator-id inD
is "derived-declarator-type-list function of(
parameter-declaration-clause)
cv-qualifier-seqopt ref-qualifieropt returningT
". The optional attribute-specifier appertains to the function type.
Delete paragraph 2.
The intent of this edit is to remove the auto->
syntax.
In a declarationT D
whereD
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 declarationT D1
is "derived-declarator-type-listT
,"T
shall be the single type-specifierauto
and the derived-declarator-type-list shall be empty. Then the type of the declarator-id inD
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.
Function declarations with late-specified return type have the form:
- function-declaration:
- func-decl-specifier-seqopt lambda-introducer id-expression function-heading
- function-heading:
(
parameter-declaration-clause)
function-qualifiers return-type-clauseopt- function-qualifiers:
mutable
opt 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 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 using the late-specified return type may be local to another function. [Example:
[] halvable(int k)->bool; { [] even(unsigned n)->bool; [] odd(unsigned n)->int { return n == 0 ? false : even(n-1); } [] even(unsigned n)->bool { return n == 0 ? true : odd(n-1); } 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-introducer are described in ([expr.prim.lambda]). A non-empty lambda-introducer shall only appear in block scope with non-
extern
declarations. These definitions are named lambdas. The capture list for a named lambda declaration and its definition must be identical. The point-of-capture for a named lambda is its definition. The invokation 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
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 declaredtypedef int IFUNC(int); IFUNC* fpif(int);
or
auto[] 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]
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 type-id 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]
Edit paragraph 1 as follows. The intent is to clarify overloading of 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]