ISO/IEC JTC1 SC22 WG21 N2582 = 08-0092 - 2008-03-16
Lawrence Crowl, crowl@google.com, Lawrence@Crowl.org
Alisdair Meredith, alisdair.meredith@codegear.com
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
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 explores the syntactic unification of function declarations and lambda expressions. It also introduces named lambdas. It 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 explore regularizing the semantics of such declarations.
Our general approach is to replace the use of auto
in
N2541
with a lambda-introducer of the form []
.
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; }
Furthermore, in keeping with discussion at the February 2008 meeting, we adjust the syntax of the new function syntax in N2541 to prevent complicated declarators. Such declarators are inconsistent with lambda expressions.
As lambda expressions can implicitly deduce the return type in simple cases, a reasonable extension to the unified syntax might allow the same for inline function definitions. However, the Evolution Working Group looked at a similar extension in some detail earlier in the process and rejected it for the coming C++0x revision. We see no reason to reverse that decision at this late stage. So, the return type is required.
Finally, for more declarative consistency, the parameter list is required, even if empty, for function declarations and named lambdas. (We do not propose to add that requirement to lambda expressions.)
For functions at namespace,
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.
(A more explicit lambda-introducer form
would be [this]
,
but we little advantage in requiring extra syntax
were none was required before.)
For functions at block scope,
an lambda-introducer of the form []
indicates a local function
without access to the containing function's local variables.
An lambda-introducer of any other form indicates a named lambda.
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 }
With these new declarations, the names declared would decay to the appropriate type:
[]
lambda-introducer
to function pointerNote that the above decay results in slightly different semantics for the following two lines:
[] f (int z)->int { return x+z; } auto f = [](int z)->int { return x+z; };
The former defines a function and will decay to a function pointer. The latter uses a lambda expression to initialize a variable containing a closure object and will not decay to a function pointer.
Finally, we come to the issue of compatiblity with existing block-local function declarations. Such declarations refer to a function at namespace scope rather than to a function at block scope. Use of such semantics has always seemed inconsistent, and so we propose to make the new syntax always declare a block-local function. Thus, forward local function declarations are reasonable.
int h(int b) { []even(unsigned n)->bool; []odd(unsigned n)->bool; []even(unsigned n)->bool { if ( n == 0 ) return true; else return odd(n-1); } []odd(unsigned n)->int { if ( n == 0 ) return false; else return even(n-1); } }
The proposed wording shows changes from an editing draft preliminary to the March 2008 mailing. The base text does not form an officially accepted draft. Earlier drafts do not have the requisite text.
In paragraph 9, edit
[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 at block scope with a simple-type-specifier of lambda-introducer introduce a new name into that scope, and theextern
specifier is not permitted.FunctionOther function declarations at block scope and 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. —end note]
In paragraph 1, edit
The simple type specifiers are
- simple-type-specifier:
::
opt nested-name-specifieropt type-name::
opt nested-name-specifieropttemplate
simple-template-idchar
char16_t
char32_t
wchar_t
bool
short
int
long
signed
unsigned
float
double
void
auto
- lambda-introducer
decltype (
expression)
In paragraph 2, edit
The
auto
specifier is a placeholder for a type to be deduced (7.1.6.4). The lambda-introducer specifier indicates a late-specified return type (8.3.5 [dcl.fct]). A lambda-introducer other than[]
may only be specified at block scope. The other simple-type-specifiers specify either a previously-declared user-defined type or one of the fundamental types (3.9.1). Table 9 summarizes the valid combinations of simple-type-specifiers and the types they specify.
In table 9, edit
Table 9: simple-type-specifiers and the types they specify Specifier(s) Type type-name the type named char
"char" unsigned char
"unsigned char" signed char
"signed char" char16_t
"char16_t" char32_t
"char32_t" bool
"bool" unsigned
"unsigned int" unsigned int
"unsigned int" signed
"int" signed int
"int" int
"int" unsigned short int
"unsigned short int" unsigned short
"unsigned short int" unsigned long int
"unsigned long int" unsigned long
"unsigned long int" unsigned long long int
"unsigned long long int" unsigned long long
"unsigned long long int" type-namethe type namedsigned long int
"long int" signed long
"long int" signed long long int
"long long int" signed long long
"long long int" long long int
"long long int" long long
"long long int" long int
"long int" long
"long int" signed short int
"short int" signed short
"short int" short int
"short int" short
"short int" wchar_t
"wchar_t" float
"float" double
"double" long double
"long double" void
"void" auto
type to be deduced lambda-introducer the late-specified return type decltype(
expression)
the type as defined below
auto
specifier [dcl.spec.auto]Remove paragraph 1.
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.
Remove paragraph 2.
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 paragraph 3.
Otherwise, the type of the object is deduced from its initializer.Theauto
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)
In summary, paragraph 4 edits are:
The edits to paragraph 4 are:
- declarator:
- direct-declarator
- ptr-operator declarator
- declarator-id parameters-and-qualifiers
->
type-id- direct-declarator:
- declarator-id
direct-declarator(
parameter-declaration-clause)
cv-qualifier-seqopt ref-qualifieropt exception-specificationopt- direct-declarator parameters-and-qualifiers
direct-declarator(
parameter-declaration-clause)
cv-qualifier-seqopt ref-qualifieropt exception-specificationopt->
type-id- direct-declarator
[
constant-expressionopt]
(
declarator)
- parameters-and-qualifers:
(
parameter-declaration-clause)
cv-qualifier-seqopt ref-qualifieropt exception-specificationopt- ptr-operator:
*
cv-qualifier-seqopt&
&&
::
opt nested-name-specifier*
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
Within paragraph 1, edit
- abstract-declarator:
- ptr-operator declaratoropt
- direct-abstract-declarator
...
- parameters-and-qualifiers
->
type-id- direct-abstract-declarator:
direct-abstract-declarator(
parameter-declaration-clause)
cv-qualifier-seqopt ref-qualifieropt exception-specificationopt- direct-abstract-declarator parameters-and-qualifiers
direct-abstact-declarator(
parameter-declaration-clause)
cv-qualifier-seqopt ref-qualifieropt exception-specificationopt->
type-id- direct-abstact-declarator
[
constant-expressionopt]
(
abstact-declarator)
In paragraph 2,
In a declaration
T D
whereD
has the form
D1 (
parameter-declaration-clause)
cv-qualifier-seqopt ref-qualifieropt exception-specificationopt->
type-idand the type of the contained declarator-id in the declaration
T D1
is "derived-declarator-type-listT
,"T
shall be the single type-specifierlambda-introducer and the derived-declarator-type-list shall be empty. If the form of the lambda-introducer isauto
[]
, thenThenthe 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. If the form of the lambda-introducer is not[]
, then the type of the declarator-id inD
is that of a closure object as specified in (5.1.1 [expr.prim.lambda]). That is, the declaration is equivalent to
auto D1 =
lambda-introducer(
parameter-declaration-clause)
exception-specificationopt->
type-id
In paragraph 3,
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:
// function returning a pointer to array[4] of int
auto[] f()->int(*)[4];
// not function returning array[4] of pointer to int—end example] —end note]
Within paragraph 12, edit
[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]
In paragraph 2, edit
The declarator in a function-definition shall have the form
D1 (
parameter-declaration-clause)
cv-qualifier-seqopt ref-qualifieropt exception-specificationopt->
type-idas described in 8.3.5. A function with a simple-type-specifier of lambda-introducer may be defined at namespace, class, or block scope. Other functions shall be defined only in namespace or class scope.