ISO/IEC JTC1 SC22 WG21 N2989 = 09-0179 - 2009-10-23
Lawrence Crowl, crowl@google.com, Lawrence@Crowl.org
Alisdair Meredith, public@alisdairm.net
This paper is a revision of N2954 = 09-0144.
Introduction
New Function Declaration Syntax
Named Lambdas
The Type of Empty–Lambda-Capture Lambdas
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]
12.3.2 Conversion functions [class.conv.fct]
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.
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 a function declaration that is 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. A program taking the address of this function call operator is ill-formed. 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 there is no lambda-capture, themutable
qualifier shall not occur. There shall be no cv-qualifier-seq or ref-qualifier in the function-qualifiers of the parameters-and-qualifiers.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]
Add a new paragraph after paragraph 5.
The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type's function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.
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
- bracket-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.
Delete paragraph 2 as follows.
The intent of this edit is to remove the auto->
syntax.
Theauto
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 ofauto
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 is 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)
The edits in this section apply to the text after applying N2927.
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
and returns to old terminology.
Declarators have the syntax
declarator:ptr-declaratornoptr-declarator parameters-and-qualifiers trailing-return-typeptr-declarator:noptrdirect-declarator- ptr-operator
ptr-declaratornoptrdirect-declarator:- declarator-id attribute-specifieropt
noptrdirect-declarator parameters-and-qualifiersnoptrdirect-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- trailing-return-type:
->
attribute-specifieropt trailing-type-specifier-seq attribute-specifieropt abstract-declaratoropt- 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).
The edits in this section apply to the text after applying N2927.
Edit within paragraph 1 as follows.
The intent of this edit
is to remove the auto->
syntax.
The change also reverts to the terminology
used before the introduction of the auto->
syntax.
- type-id:
- type-specifier-seq attribute-specifieropt abstract-declaratoropt
- abstract-declarator:
- ptr-abstract-declarator
noptr-abstract-declaratoropt parameters-and-qualifiers->
trailing-return-type...
- ptr-abstract-declarator:
noptr-direct-abstract-declarator- ptr-operator ptr-abstract-declaratoropt
noptr-direct-abstract-declarator:noptr-direct-abstract-declaratoropt parameters-and-qualifiersnoptr-direct-abstract-declaratoropt[
constant-expression]
attribute-specifieropt(
ptr-abstract-declarator)
The edits in this section apply to the text after applying N2927.
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 of(
parameter-declaration-clause)
cv-qualifier-seqopt ref-qualifieropt returningT
", using the components of parameters-and-qualifiers. 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 trailing-return-type
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". The optional attribute-specifier appertains to the function type.
Insert a new paragraph 2. The intent of this edit is to add the new function syntax.
Function declarations with trailing-return-type have the form:
- bracket-function-declaration:
- decl-specifier-seqopt lambda-introducer id-expression lambda-declarator
The decl-specifier-seq of a bracket-function-declaration may contain only those decl-specifiers that apply to functions:
static
,extern
,friend
,typedef
,constexpr
, and function-specifier. The function-qualifiers shall containmutable
only in the context of a bracket-function-declaration or lambda-expression with a non-empty lambda-capture.
Insert a new paragraph after the above paragraph.
If a bracket-function-declaration is at block-scope and not declared
extern
, it shall be part of a function-definition [Footnote: This restriction prohibits forward declarations. —end footnote], the id-expression shall be an identifier, and the decl-specifier-seq shall be empty. Such a bracket-function-declaration defines a variable of closure type initialized with the corresponding lambda expression and is called a named lambda; no other kind of function-definition is allowed at block scope. Otherwise, a bracket-function-declaration declares id-expression to be of type "function of(
parameter-declaration-clause)
cv-qualifier-seqopt ref-qualifieropt returningT
", from components of lambda-declarator whereT
is the type denoted by the trailing-return-type of the lambda-declarator. The trailing return type is required, and the lambda-capture shall be empty.[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; } [] halvable(int k)->bool { [] even(unsigned n)->bool { return (n & 1) == 0; } return even(k); }
—end example]
Edit paragraph 4 as follows.
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
- decl-specifier-seq attribute-specifieropt declarator
=
assignment-expression- decl-specifier-seq attribute-specifieropt abstract-declaratoropt
- decl-specifier-seq attribute-specifieropt abstract-declaratoropt
=
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 trailing-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 trailing-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 ;
- bracket-function-declaration function-body
- function-body:
- ctor-initializeropt compound-statement
- function-try-block
= default ;
= delete ;
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 ;
bracket-function-declaration= default ;
it 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 ;
bracket-function-declaration= delete ;
it 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. [Note: Non-extern block-scope bracket-function-declarations cannot be overloaded. [Example:
double a(int n) { [n] p(int m) -> int { return m+n; } [n] p(double m) -> double { return m+n; }
// error: duplicate variablereturn p(3.0); }
—end example] —end note]