Default constructed lambda closures

339 views
Skip to first unread message

Róbert Dávid

unread,
Dec 30, 2012, 5:10:27 PM12/30/12
to std-pr...@isocpp.org
Hello everyone,

what would you think about lambda closure types with empty capture lists having a default constructor?

Why do I think it would be beneficial? I want to something similar to:
struct Rect {
   
double a;
   
double b;
};

typedef std
::set<Rect, decltype([](const Rect& l, const Rect& r){ return l.a*l.b < r.a*r.b; })> RectsByArea;

typedef std::set<Rect, decltype([](const Rect& l, const Rect& r){ return l.a+l.b < r.a+r.b; })> RectsByCirc;

typedef std::set<Rect, decltype([](const Rect& l, const Rect& r){ return l.a*l.a+l.b*l.b < r.a*l.a+r.b*r.b; })> RectsByDiag;

RectsByArea ra;
RectsByCirc rc;
RectsByDiag rd;

Using C++/11 this will fail with the lambda not having a default constructor. Why? The default constructor of std::set is actually a two-parameter constructor with default parameters: first to the comparator, the second to the allocator. Now the allocator here is not the problem, but the comparator, what is here the lambda - and it does not have a default constructor.

For some compilers, there is actually a more-less usable workaround that almost does what I wanted (tested with Visual Studio 2010 and GCC 4.6):
auto byArea = [](const Rect& l, const Rect& r){ return l.a*l.a+l.b*l.b < r.a*l.a+r.b*r.b; }
std::set<Rect, decltype(byArea)> rectsByArea(byArea);
The code here is still (somewhat) local, but for some functions it explodes: for example, the copy assignment operator - being a template, we are good until we want to use them... Also, it breaks under Visual Studio 2012 (and possibly more compilers as well): there, if the comparator has a sizeof of 0, it does not store it, just default-constructs when needed - with the lambda, we are back to square one.

I know this can be avoided by having a few plain old functors with operator(), but the whole idea of lambdas is that the functor is written in-line.

The change to the standard would be "minor": in section 5.1.2.19, it currently states (at least in the latest public draft n3337):

The closure type associated with a lambda-expression has a deleted (8.4.3) default constructor and a deleted
copy assignment operator.

This should be changed to define a default constructor for closures without lambda captures, the same way it defines a conversion operator to a function pointer for them - something similar to:

The closure type for a lambda-expression with no lambda-capture has a public implicitly-declared (12.8) default constructor. Closure types associated with a lambda-expression with more than zero lambda-captures have a deleted (8.4.3) default constructor and a deleted copy assignment operator.

My only concern if default construction could break something - but I could not find anything like that.
What do you think?

Nicol Bolas

unread,
Dec 30, 2012, 6:20:29 PM12/30/12
to std-pr...@isocpp.org
Lambda functions are not intended to be a quick way of writing a functor that you intend to use from multiple places. It's for writing a function locally which you can pass as callbacks or to algorithms and the like. They exist to make code more readable and make it more obvious what you're doing.

Just make a functor. It's not that hard, and it will be far more readable for the user. I certainly don't want to see IntelliSense tell me that the typedef RectByArea is "std::set<Rect, decltype([](const Rect& l, const Rect& r){ return l.a*l.b < r.a*r.b; })>".

Róbert Dávid

unread,
Dec 30, 2012, 6:44:39 PM12/30/12
to std-pr...@isocpp.org
I agree that lambdas are made only to pass callbacks and to algorithms and the like - this is 'the like' part :) Take note, the example is watered down for easier understanding. I have found quite a few times that having a lambda - a locally written functor - passed as a comparator to a container would produce a lot cleaner code (and with the workaround, it does, if one can live with its current drawbacks).

Lambdas are a way to write a functor locally, and that is exactly what I am looking for. Why making the restriction of disallowed re-usage? For some cases (like in the example), the lack of the default constructor prevents that - and if it does not break anything else, why preventing.

For a compiler, no-capture lambdas are already special case because of the conversion to function pointer, so this is not too complicating to implement.

It is not hard to make a functor, but extending that logic, why do we have lambdas at all?

Nicol Bolas

unread,
Dec 30, 2012, 8:08:59 PM12/30/12
to std-pr...@isocpp.org

Because lambdas improve code locality. 99 times out of 100, if you're instantiating a template, you're probably going to instantiate it in more than one function. Especially given your example of a typedef in a header.

Within a function, in C++98, you couldn't create a struct and use it in a template. Lambdas are a way to put code in the function where it is used. We don't have that problem in a header like this. You're not harming code locality by moving the "lambda" one line up like this:

struct CompareByArea{ bool operator()(const Rect& l, const Rect& r){ return l.a*l.b < r.a*r.b; }};
typedef std::set<Rect, CompareByArea> RectsByArea;

Plus, odds are good that you will want to use `CompareByArea` in more than one place.

It's not a question of implementation burden. It's a question of whether we should ever encourage users to type "decltype([]...)". I do not think we should; nothing good comes of that.

Nevin Liber

unread,
Dec 31, 2012, 10:43:57 AM12/31/12
to std-pr...@isocpp.org
On 30 December 2012 16:10, Róbert Dávid <lrd...@gmail.com> wrote:
Hello everyone,

what would you think about lambda closure types with empty capture lists having a default constructor?

+1.


For some compilers, there is actually a more-less usable workaround that almost does what I wanted (tested with Visual Studio 2010 and GCC 4.6):
auto byArea = [](const Rect& l, const Rect& r){ return l.a*l.a+l.b*l.b < r.a*l.a+r.b*r.b; }
std::set<Rect, decltype(byArea)> rectsByArea(byArea);

As most of my sets/maps tend to be class members, the above technique doesn't work, and I end up writing explicit function object types to deal with it.

The change to the standard would be "minor": in section 5.1.2.19, it currently states (at least in the latest public draft n3337):

The closure type for a lambda-expression with no lambda-capture has a public implicitly-declared (12.8) default constructor.

 I think the above is the only change you need.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404
Reply all
Reply to author
Forward
0 new messages