Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are captureless lambdas structural types?

P1907R1, accepted for C++20, introduced structural types, which are a valid types for non-type template parameter.

GCC and Clang both accepts the following snippet for C++2a:

template<auto v>
constexpr auto identity_v = v;

constexpr auto l1 = [](){};
constexpr auto l2 = identity_v<l1>; 

implying that the type of a captureless lambda is a structural type.

Question

  • Does a captureless lambda indeed fulfill the requirements for its type to to be a structural type?
like image 484
dfrib Avatar asked Oct 21 '20 10:10

dfrib


People also ask

Are C++ lambdas closures?

In C++, lambda expression constructs a closure, an unnamed function object capable of capturing variables in scope.

What are lambdas in C++?

In C++11 and later, a lambda expression—often called a lambda—is a convenient way of defining an anonymous function object (a closure) right at the location where it's invoked or passed as an argument to a function.

What is capture list in C++?

The capture list defines the outside variables that are accessible from within the lambda function body. The only capture defaults are. & (implicitly capture the used automatic variables by reference) and. = (implicitly capture the used automatic variables by copy).

How do you pass a lambda function in C++?

Permalink. All the alternatives to passing a lambda by value actually capture a lambda's address, be it by const l-value reference, by non-const l-value reference, by universal reference, or by pointer.


1 Answers

All standard references below, unless explicitly noted otherwise, refers to N4861 (March 2020 post-Prague working draft/C++20 DIS).


The type of a captureless lambda (its closure type) is a structural type

Henceforth, we will refer to the type of the lambda solely as the closure type.

As shown through the standard passages below, the closure type of a captureless lambda:

  • fulfills the requirements for it to be a literal (class) type, and moreover
  • fulfills the requirements on a literal type for it to be a structural type,

and may thus be used as the type for a non-type template parameter, such that the example snippet

template<auto v>
constexpr auto identity_v = v;

constexpr auto l1 = [](){};
constexpr auto l2 = identity_v<l1>;

is indeed well-formed.


The closure type of a lambda is a non-union class type

As governed by [expr.prim.lambda.closure]/1 [emphasis mine]

The type of a lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type, called the closure type, whose properties are described below.

the closure type is a non-union class type.


The closure type of a captureless lambda is a literal (class) type

As governed by [basic.types]/10 [extract, emphasis mine]

A type is a literal type if it is:

  • [...]
  • a possibly cv-qualified class type that has all of the following properties:
    • it has a constexpr destructor ([dcl.constexpr]),
    • it is either a closure type ([expr.prim.lambda.closure]), an aggregate type ([dcl.init.aggr]), or has at least one constexpr constructor or constructor template (possibly inherited from a base class) that is not a copy or move constructor,
    • if it is a union, at least one of its non-static data members is of non-volatile literal type, and
    • if it is not a union, all of its non-static data members and base classes are of non-volatile literal types.

the closure type is a literal type if

  • has a constexpr destructor, and if
  • all of its non-static data members are of non-volatile literal types.

The closure type of a captureless lambda has no non-static data members, so the latter requirement is fulfilled. What about the former, a constexpr destructor?

Implicitly generated constexpr destructor

As governed by [expr.prim.lambda.closure]/14

The closure type associated with a lambda-expression has an implicitly-declared destructor ([class.dtor]).

the destructor of the closure type is declared implicitly. Furthermore, [/dcl.fct.def.default]/5 describes [extract, emphasis mine]

Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them ([class.ctor], [class.dtor], [class.copy.ctor], [class.copy.assign]), [...]

that the collective term defaulted functions also include implicitly-declared destructors.

Finally, [class.dtor]/9

A defaulted destructor is a constexpr destructor if it satisfies the requirements for a constexpr destructor ([dcl.constexpr]).

describe that defaulted destructors are constexpr destructors if they fulfill the requirements of [dcl.constexpr], particularly [dcl.constexpr]/3 and [dcl.constexpr]/5 [extracts, emphasis mine]

[dcl.constexpr]/3 The definition of a constexpr function shall satisfy the following requirements:

  • [...]
  • if the function is a constructor or destructor, its class shall not have any virtual base classes;
  • [...]

[dcl.constexpr]/5 The definition of a constexpr destructor whose function-body is not = delete shall additionally satisfy the following requirement:

  • for every subobject of class type or (possibly multi-dimensional) array thereof, that class type shall have a constexpr destructor.

all of which are fulfilled for the closure type of a captureless lambda (no base classes, and no subobjects; see [intro.object]/2 for the latter).

Thus, the closure type of captureless lambda is a literal type.


The closure type of a captureless lambda is a structural type

As per [temp.param]/6 and [temp.param]/7 [extract, emphasis mine]

[temp.param]/6 A non-type template-parameter shall have one of the following (possibly cv-qualified) types:

  • a structural type (see below),
  • [...]

[temp.param]/7

A structural type is one of the following:

  • a scalar type, or
  • an lvalue reference type, or
  • a literal class type with the following properties:
    • all base classes and non-static data members are public and non-mutable and
    • the types of all bases classes and non-static data members are structural types or (possibly multi-dimensional) array thereof.

a literal class type is trivially a structural type if it has no base classes and no non-static data members. Both of these holds for a captureless lambda, and thus, the closure type of a captureless lambda is a structural type.


Some notes on the original intent of allowing the closure type of a lambda to be a literal type

N4487 proposed allowing certain lambda-expressions and operations on certain closure objects to appear within constant expressions, and contained a dedicated section to the topic of a closure type being a literal type:

The closure object should be a literal type if the type of each of its data­ members is a literal type.

A closure type in C++14 can never be a literal type – even if all its data members are literal types – because it lacks a constexpr constructor that is not a copy or move constructor. If such a closure type was allowed to have an implicitly defined default constructor it would be constexpr, making it a literal type. But, because closure types, by definition, must have their default constructors deleted, the implementation is prohibited from implicitly defining one. [...]

P0170R1, containing the core wording from N4487, was accepted and implemented for C++17.

At this time (C++14 and C++17), however, a destructor could not be constexpr, and thus there naturally existed no requirement for a literal type to have a constexpr destructor; [basic.types]/10.5.1 in N4140 (C++14) as well as [basic.types]/10.5.1 in N4659 (C++17) instead required destructor to be trivial:

A type is a literal type if it is:

  • [...]
  • a class type (Clause [class]) that has all of the following properties:
    • it has a trivial destructor,
    • [...]

P1907R1, accepted for C++20, expanded the requirement for template parameter objects to have constant destruction; [temp.param]/8 [emphasis mine]:

An id-expression naming a non-type template-parameter of class type T denotes a static storage duration object of type const T, known as a template parameter object, whose value is that of the corresponding template argument after it has been converted to the type of the template-parameter. All such template parameters in the program of the same type with the same value denote the same template parameter object. [...] A template parameter object shall have constant destruction.

and, P0784R7, also accepted for C++20, particularly contained the introduction of constexpr destruction, including the update to the requirement for a type to be a literal type; particularly described in an earlier version of the paper, P0784R1:

The proposed rules for constexpr destructors are:

  • [...]
  • A literal type requires a constexpr destructor (previously, the stronger requirement of a trivial destructor was made)
like image 51
dfrib Avatar answered Oct 22 '22 19:10

dfrib