Unified Function Syntax

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.

Syntactic Unification

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.)

Semantic Regularization

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:

Note 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);

Proposed Wording

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.

3.3.1 Point of declaration [basic.scope.pdecl]

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 ( Function declarations at block scope with a simple-type-specifier of lambda-introducer introduce a new name into that scope, and the extern specifier is not permitted. Function Other function declarations at block scope and object declarations with the extern 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] Simple type specifiers [dcl.type.simple]

In paragraph 1, edit

The simple type specifiers are

::opt nested-name-specifieropt type-name
::opt nested-name-specifieropt template simple-template-id
decltype ( expression )

In paragraph 2, edit

The auto specifier is a placeholder for a type to be deduced ( 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
type-namethe type named
unsigned char"unsigned char"
signed char"signed char"
unsigned"unsigned int"
unsigned int"unsigned int"
signed 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 named
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"
long double"long double"
autotype to be deduced
lambda-introducerthe late-specified return type
decltype(expression)the type as defined below auto specifier [dcl.spec.auto]

Remove paragraph 1.

The auto 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.

The auto 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 of auto 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. The auto 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 )

Chapter 8 Declarators [dcl.decl]

In summary, paragraph 4 edits are:

The edits to paragraph 4 are:

ptr-operator declarator
declarator-id parameters-and-qualifiers -> type-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 )
( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifieropt exception-specificationopt
* cv-qualifier-seqopt
::opt nested-name-specifier * cv-qualifier-seqopt
cv-qualifier cv-qualifier-seqopt
...opt id-expression
::opt nested-name-specifieropt class-name

8.1 Type names [dcl.name]

Within paragraph 1, edit

ptr-operator declaratoropt
parameters-and-qualifiers -> type-id
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 )

8.3.5 Functions [dcl.fct]

In paragraph 2,

In a declaration T D where D 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 declaration T D1 is "derived-declarator-type-list T," T shall be the single type-specifier auto lambda-introducer and the derived-declarator-type-list shall be empty. If the form of the lambda-introducer is [], then Then the type of the declarator-id in D 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 in D 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:

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]

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 declared

typedef int IFUNC(int);
IFUNC* fpif(int);


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]

8.4 Function definitions [dcl.fct.def]

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-id

as 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.