Understanding How Lambda Closure Type Has Deleted Default Constructor

Understanding how Lambda closure type has deleted default constructor

The relationship between a closure to lambda is similar to object to class.

The C++11 standard says that the closure! type has no default constructor, and that is correct because it doesn't say it has no constructor.

The lambda is used to create a closure. But your quoted paragraph will change for C++14.

ClosureType() = delete;                     // (until C++14)
ClosureType(const ClosureType& ) = default; // (since C++14)
ClosureType(ClosureType&& ) = default; // (since C++14)

Closure types are not DefaultConstructible. Closure types have a deleted (until C++14) no (since C++14) default constructor. The copy constructor and the move constructor are implicitly-declared (until C++14) declared as defaulted (since C++14) and may be implicitly-defined according to the usual rules for copy constructors and move constructors.

http://en.cppreference.com/w/cpp/language/lambda

Lambda closure type constructors

There was a shortcoming. We couldn't use lambdas quite as "on the fly" as one might have wanted. C++20 (with the addition of allowing lambdas in unevaluated contexts) makes this code valid:

struct foo {
int x, y;
};

std::map<foo, decltype([](foo const& a, foo const& b) { return a.x < a.y; })> m;

Note how we defined the compare function inline? No need to create a named functor (could be a good idea otherwise, but we aren't forced too). And there's no need to break the declaration in two:

// C++17
auto cmp = [](foo const& a, foo const& b) { return a.x < a.y; };
std::map<foo, decltype(cmp)> m(cmp); // And also need to pass and hold it!

Usages like this (and many more) were the motivating factor in making this change. In the example above, the anonymous functors type will bring all the benefits a named functor type can bring. Default initialization and EBO among them.

MSVC behaves different about default constructor of closure type in C++20

The standard is pretty clear here. In [expr.prim.lambda.closure]/13:

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.

This rule is based on the lexical makeup of the lambda, not the semantic analysis that we do to decide if there is anything captured. It is a grammatical distinction.

If a lambda starts with [], then it has no lambda-capture, and thus has a defaulted default constructor.

If a lambda starts with [&], then it has a lambda-capture, and thus has no default constructor - regardless of whether anything is captured. It doesn't matter if anything is captured or not.

The clarification that cppreference adds here is correct, and helpful. The lambda [&](){} is not default constructible (or, by the same logic, assignable). So, yes this is a MSVC bug.


Note that this is the same kind of rule that we have to determine if a lambda can be converted to a function pointer: whether or not there is a lambda-capture, not whether or not there is any capture. Thus, [](){} is convertible to void(*)() but [&](){} is not.

Why closure types / lambdas have no default constructor in C++

Lambdas in C++ are a syntactic convenience to allow the programmer to avoid declaring a functor using traditional syntax like

struct my_functor
{
bool operator()(Object &) { /* do something */ return false; }
}

because writing functors using this syntax is considered too long winded for simple functions.

Lambdas offer parameter capture options e.g [=], [&] etc. , to save you declaring variables and initializing them, like you might do in the constructor of a functor object.

So Lambdas don't expose any constructor syntax to you by design.

There is no technical reason a lambda function could not expose a constructor, however if it were to do so it would stop mimicking the design concept it is named for.


With thanks to the commentators under the question.

How does the compiler generate a closure from a deleted-default-ctor lambda?

This is a bit like asking: if int has no constructor, then where does the int object come from when an integer literal is evaluated?

The compiler can create objects without calling a constructor, simply by generating the assembly or machine code that is required to set up the object in memory. It just doesn't let YOU create objects of various class types without calling a constructor. (Although, in C++20, this will change thanks to P0593.)

When the compiler sees an expression that contains a lambda-expression, it just goes ahead and generates the code that sets up the closure object in memory. It is not obligated to package that code as a function, the way the code in a constructor would be packaged. It's even possible that the compiler generates some constructor that only it knows how to call. From the user's point of view, there is no constructor involved.

Sort a container with a lambda with captured value

You can't pass a runtime variable in a template parameter.

The Compare template parameter of std:set expects a typename, not a variable. The actual comparison function matching the typename can be passed to the set::set constructor.

Try this instead:

//Using a lambda

int value = 3; //This is not known at compile time

auto cmp = [&value](int a, int b){
return value > 123 ? a < b : a >= b;
};

std::set<int, decltype(cmp)> mySet(cmp);
//Using a functor

class MySetComparer{
int value;
public:
MySetComparer(int value) : value(value) {}
bool operator()(int a, int b) const {
return value > 123 ? a < b : a >= b;
}
};

int val = 3; //This is not known at compile time

MySetComparer cmp(val);
set<int, MySetComparer/*decltype(cmp)*/> mySet(cmp);

NOTE: either way, do be aware that your comparer's use of a >= b breaks strict weak ordering of the Compare requirement used by standard C++ containers:

The return value of the function call operation applied to an object of a type satisfying Compare, when contextually converted to bool, yields true if the first argument of the call appears before the second in the strict weak ordering relation induced by this type, and false otherwise.

a < b satisfies that requirement, but a >= b does not, which will cause Undefined Behavior in your code at runtime when value > 123 is not true.

Why is a lambda in C++ never DefaultConstructible

Lambdas are intended to be created then used. The standard thus says "no, they don't have a default constructor". The only way to make one is via a lambda expression, or copies of same.

They are not intended for their types to be something you keep around and use. Doing so risks ODR violations, and requiring compilers to avoid ODR violations would make symbol mangling overly complex.

However, in C++17 you can write a stateless wrapper around a function pointer:

template<auto fptr>
struct function_pointer_t {
template<class...Args>
// or decltype(auto):
std::result_of_t< std::decay_t<decltype(fptr)>(Args...) >
operator()(Args&&...args)const
return fptr(std::forward<Args>(args)...);
}
};

And as operator void(*)() on [](){} is constexpr in C++17, function_pointer_t<+[](){}> is a do-nothing function object that is DefaultConstructible.

This doesn't actually wrap the lambda, but rather the pointer-to-function that the lambda produces.



Related Topics



Leave a reply



Submit