Home > database >  Construction of lambda object in case of specified captures in C
Construction of lambda object in case of specified captures in C

Time:10-23

Starting from C 20 closure types without captures have default constructor, see https://en.cppreference.com/w/cpp/language/lambda:

If no captures are specified, the closure type has a defaulted default constructor.

But what about closure types that capture, how can their objects be constructed?

One way is by using std::bit_cast (provided that the closure type can be trivially copyable). And Visual Studio compiler provides a constructor for closure type as the example shows:

#include <bit>

int main() {
    int x = 0;
    using A = decltype([x](){ return x; }); 

    // ok everywhere
    constexpr A a = std::bit_cast<A>(1);
    static_assert( a() == 1 );

    // ok in MSVC
    constexpr A b(1);
    static_assert( b() == 1 );
}

Demo: https://gcc.godbolt.org/z/dnPjWdYx1

Considering that both Clang and GCC reject A b(1), the standard does not require the presence of this constructor. But can a compiler provide such constructor as an extension?

CodePudding user response:

But what about closure types that capture, how can their objects be constructed?

You can't. They can only be created from the lambda expression.

And no, bit_cast does not "work everywhere". There is no rule in the C standard which requires that any particular lambda type must be trivially copyable (or the same size as its capture member for that matter). The fact that no current implementations break your code does not mean that future implementations cannot.

And it definitely won't work if you have more than one capture member.

Just stop treating lambdas like a cheap way to create a type. If you want to make a callable type with members that you can construct, do that:

#include <bit>

int main() {
    struct A
    {
      int x = 0;
      constexpr auto operator() {return x;}
    };

    // ok everywhere
    constexpr A b(1);
    static_assert( b() == 1 );
}

CodePudding user response:

Since this is tagged language-lawyer, here's what the C standard has to say about all this.

But what about closure types that capture, how can their objects be constructed?

The actual part of the standard that cppreference link is referencing is [expr.prim.lambda.general] - 7.5.5.1.14:

The closure type associated with a lambda-expression has no default constructor if the lambda-expression has a lambda-capture and a defaulted default constructor otherwise. It has a defaulted copy constructor and a defaulted move constructor ([class.copy.ctor]). It has a deleted copy assignment operator if the lambda-expression has a lambda-capture and defaulted copy and move assignment operators otherwise ([class.copy.assign]).

However, clauses 1 and 2 say:

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 not an aggregate type. An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by changing: [unrelated stuff]

Which means that (apart from the unrelated exceptions), the described interface of the lambda as stated is exhaustive. Since no other constructors than the default one is listed, then that's the only one that is supposed to be there.

N.B. : A lambda may be equivalent to a class-based functor, but it is not purely syntactical sugar. The compiler/implementation does not need a constructor in order to construct and parametrize the lambda's type. It's just programmers who are prevented from creating instances by the lack of constructors.

As far as extensions go:

But can a compiler provide such constructor as an extension?

Yes. A compiler is allowed to provide this feature as an extension as long as all it does is make programs that would be ill-formed functional.

From [intro.compliance.general] - 4.1.1.8:

A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this document. Having done so, however, they can compile and execute such programs.

However, for the feature at hand, MSVC would be having issues in its implementation as an extension:

  1. It should be emmiting a diagnostic.
  2. By its own documentation, it should refuse the code when using /permissive-. Yet it does not.

So it looks like MSVC is, either intentionally or not, behaving as if this was part of the language, which is not the case.

  • Related