ISO/IEC JTC1 SC22 WG21 N2931 = 09-0121 - 2009-07-17
Lawrence Crowl, crowl@google.com, Lawrence@Crowl.org
Alisdair Meredith, public@alisdairm.net
This paper is a revision of N2890 = 09-0080.
Introduction
New Function Declaration Syntax
Named Lambdas
The Type of Empty–Lambda-Capture Lambdas
Auto with Function Definitions
Proposed Wording
3.3.2 Point of declaration [basic.scope.pdecl]
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]
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. comment 42, and U.S. comment 43 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. This paper presents a unification that includes less capability than in some later papers. This change in approach addresses a concern that those later papers presented too much technical risk.
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.
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 new function syntax at block scope named lambdas.
int h(int b) { [] m(int z)->int { return x+z; } // b is not in scope [&] n(int z)->int { 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); }
The advantage to named lambdas over this auto variable approach is that it is possible to do forward declarations and overloading. However, due to technical risk, those possibilities are not supported at this time.
Due to concerns about the effects of argument-dependent lookup, no operator names are permitted on named 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.
We adopt the suggested resolution.
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; }
Due to technical risk,
the return type must consist only of auto
.
The proposed wording shows changes from working draft N2914, presuming that the edits from N2927 New wording for C++0x Lambdas (rev 2) have been already applied.
Edit paragraph 9 as follows. The intent is to enable named lambdas while preserving existing semantics for existing code.
[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 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 that are not a 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.
Edit the syntax in paragraph 1 as follows. This edit refactors the lambda syntax to reuse the function syntax, which includes modifications to support lambda.
- lambda-expression:
- lambda-introducer lambda-declaratoropt 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-declarator:
(
parameter-declaration-clause)
attribute-specifieroptmutable
opt exception-specificationopt trailing-return-typeopt- parameters-and-qualifiers trailing-return-typeopt
Edit paragraph 4 as follows.
If a lambda-expression does not include a lambda-declarator, it is as if the lambda-declarator were
()
. If a lambda-expression does not include a trailing-return-type, or if the trailing-return-type is->auto
, it is as if the trailing-return-type denotes the following type:
if the compound-statement is of the form
{ return
attribute-specifieropt expression; }
the type of the returned expression after lvalue-to-rvalue conversion (_conv.lval_ 4.1), array-to-pointer conversion (_conv.array_ 4.2), and function-to-pointer conversion (_conv.func_ 4.3);
- otherwise, void.
[Example:
auto x1 = [](int i){ return i; };
//
OK: return type is int
auto x2 = []{ return { 1, 2 }; };
//
error: the return type is void (a braced-init-list is not an expression)—end example]
Edit paragraph 5 as follows. The intent of this edit is to obtain a closure-to-function-pointer conversion with no lambda-capture and to restrict the qualifiers available from the now more general syntax for parameters-and-qualifiers.
The closure type for a lambda-expression has a public inline function call operator (_over.call_ 13.5.4) whose parameters and return type are described by the lambda-expression's parameter-declaration-clause and trailing-return-type respectively. This function call operator is declared
const
(_class.mfct.non-static_ 9.3.1) if and only if the lambda-expression's parameter-declaration-clause is not followed bymutable
. When the there is no lambda-capture, themutable
qualifier shall not occur; the function call operator is declaredstatic
; and a conversion operator to pointer to the type of the function call operator is defined to return a pointer to the call operator function. There shall be no cv-qualifier-seq or ref-qualifier following the parameter-declaration-clause.ItThe function call operator is not declaredvolatile
. Default arguments (_decl.fct.default_ 8.3.6) shall not be specified in the parameter-declaration-clause of a lambda-declarator. Any exception-specification specified on a lambda-expression applies to the corresponding function call operator. Any attribute-specifiers appearing immediately after the lambda-expression's parameter-declaration-clause appertain to the type of the corresponding function call operator. [Note: Names referenced in the lambda-declarator are looked up in the context in which the lambda-expression appears. —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
- 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
- ....
auto
specifier [dcl.spec.auto]
Edit paragraph 1 as follows.
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 initializeror 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.
The
auto
type-specifier may appearwithas the return type of 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]) and signifies that the return type shall be deduced from the function-body.auto
is replaced by the type specified at the end of the declarator.
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 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
- function-qualifiers:
- attribute-specifieropt
mutable
opt cv-qualifier-seqopt ref-qualifieropt exception-specificationopt- ptr-operator:
*
attribute-specifieropt cv-qualifier-seqopt&
&&
::
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 within paragraph 1 as follows.
The intent of this edit
is to remove the auto->
syntax.
- type-id:
- type-specifier-seq attribute-specifieropt abstract-declaratoropt
- 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
parameters-and-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 ofparameters-and-qualifiers returning(
parameter-declaration-clause)
cv-qualifier-seqopt ref-qualifieroptT
". 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 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.
Function declarations with late-specified return type have the form:
- function-declaration:
- decl-specifier-seqopt lambda-introducer id-expression lambda-declarator
[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 shall containmutable
only when part of a function-declaration or lambda-expression with a non-empty lambda-capture. The id-expression of a function-declaration within block scope shall contain neither a operator-function-id nor a conversion-function-id. A non-extern block-scope 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 trailing-return-type.
Within a lambda-declarator of a function-declaration, a missing trailing-return-type indicates that the return type is
void
, unless the heading is part of a definition with a function-body of the form{ return
expression; }
, in which case the return type is deduced as in (5.1.2 [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.
Function-definitions may be local to another function. These definitions are named lambdas. A named lambda defines a variable of closure type equivalent to the corresponding lambda expression. [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.
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 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 function-body is{ return
expr; }
, and that the return type specification contains only theauto
specifier. The return type is deduced as in (5.1.2 [expr.prim.lambda]). [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:If a function definition has one of the 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:If a function definition has one of the forms:decl-specifier-seqopt attribute-specifieropt declarator
= delete ;
function-declaration= delete ;
is called a deleted definition. ...
Edit paragraph 1 as follows. The intent is to prohibit 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 declarations can be overloaded; object and type declarations cannot be overloaded. Futhermore, non-extern block-scope function-declarations 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 overloadreturn p(3.0); }
—end example]