5

I have an interesting problem with C++ lambdas. The use case is a performance-critical thread that constructs a lambda with non-empty closure which is then passed to a different thread so it can be "worked" on.

The overall idea works but my lambda closure object gets copied one extra time and compiler (currently g++ 5.4.0) is not able to optimize away this copy including with copy elision. I also tried clang with similar outcome.

Ideally I would like to use a placement new when creating lambda objects directly in the durable memory instead of first creating object on stack for example:

auto l = new (buf) [a,b,c](){}

The error is something like:

lambda_ctor.cpp:39:19: error: expected type-specifier before '[' token
    auto al = (buf) new [a,b,c](){};

I was thinking maybe if I had a type in hand instead of actual lambda expression compiler should accept it but trying to lift type of closure without evaluating it runs into another problem:

using T = decltype([a,b,c](){});

with this error:

lambda_ctor.cpp:41:24: error: lambda-expression in unevaluated context
    using T = decltype([a,b,c](){});

I can't seem to find a way around this, there are a number of ideas on how to keep lambda from disintegrating on scope exit but every solution seems to involve copying/moving the closure object once it has been created (aka extra copy). This is a little strange because normally we have full control over how a user-defined functor is created and passed around and similar rules should apply to closures.

rtz
  • 61
  • 3

2 Answers2

3

auto is your friend.

auto l = new (buf) auto([a,b,c](){});

Any copy of the closure object should be elidable.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • This is exactly what I was hoping for, no copy is created and closure members are initialized in place, thank you very much! – rtz Jun 30 '16 at 17:19
  • When was this added? 14 or 17? Wait a sec, [11?!](http://en.cppreference.com/w/cpp/language/new) [yep](https://stackoverflow.com/questions/15935080/what-does-new-auto-do), I had no clue. – Yakk - Adam Nevraumont Jun 30 '16 at 20:52
  • @Yakk Actually, I'm fairly sure 11. – T.C. Jun 30 '16 at 20:54
3

You can do something like

auto l = new (buf) auto([a, b, c]{});

But you have to know that buf has enough space to hold the lambda. In order to find the size of the actual lambda you would need to do something like

auto l = [a, b, c]{};
auto size = sizeof(l);

But now you've created the lambda on the stack, which you didn't want to do. But now you know how big the buffer has to be, and can do

auto buf = new char[size];
auto lptr = new (buf) auto(std::move(l));

And the lambda will be moved into the buffer.

Eventually, though, you will need to destruct the lambda and free the buffer. The lambda can be easily destructed with a template function.

template<typename Lambda>
void destruct(Lambda* l)
{
    l->~Lambda();
}
evan
  • 1,463
  • 1
  • 10
  • 13
  • 1
    Here is a way to find out size of capture by using un-evaluated branch: template T* get_ptr(T t) { return nullptr; } auto * l = false ? get_ptr(lambda goes here) : nullptr; enum { lambda_size = sizeof(*l); } – rtz Jul 01 '16 at 12:54
  • @rtz That lambda does not have to be the same size as a lambda on the very next line with the same body... – Yakk - Adam Nevraumont Jul 18 '16 at 14:17