ISO/IEC JTC1 SC22 WG21 N2763 = 08-0273 - 2008-09-19
Lawrence Crowl, crowl@google.com, Lawrence@Crowl.org
Alisdair Meredith, alisdair.meredith@codegear.com
This paper is a revision of N2582 = 08-0092 and reflects the consensus of the "other" library subgroup on 15 September 2008. The revision removes the handling of the nested declarator issue, as core language issue 681 addresses the problem. The revision removes the introduction of named lambdas and block-local function definitions. The intent is that these features be added in a later standard.
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 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; }
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. (We do not propose to add that requirement to lambda expressions.)
The intent of unified function syntax is to enable future language features as compatible extensions. We discuss those extensions here, but stress that the proposed wording does not introduce named lambdas or nested functions.
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 working draft standard N2723.
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 and object declarations with theThe following kinds of block scope declarations refer to delarations that are members of an enclosing namespace, but they do not introduce new names into that scope.extern
specifier at block scope
- function declarations without a simple-type-specifier of lambda-introducer,
- function declarations with a simple-type-specifier of lambda-introducer and with an
extern
specifier,- object declarations with the
extern
specifier.—end note]
In paragraph 6, edit
The name of a function declared in block scope without a simple-type-specifier of lambda-introducer, and the name of a function or an object declared by a block scope extern declaration, have linkage. If a declared function has no linkage, the program is ill-formed. 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.
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 is used only for a late-specified return type (8.3.5 [dcl.fct]), and shall have the form[]
. 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" signed 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 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.auto
Then theThe 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.
Note that N2761 may add attribute specifiers to the declaration form above. That edit does not conflict. Note that N2757 may remove "and the derived-declarator-type-list shall be empty" in the text above.
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]