A problem with C++ lambdas?

C++ lambdas are wonderful for all sorts of reasons (especially with their C++14-and-beyond power). But I’ve run into a problem that I can’t think of a good way around yet.

If you’re up to date with C++, of course you know that rvalue references and move semantics are a major thing. At this point, there are a lot of blog posts, youtube/conference videos, and even books about how they work, and also about how forwarding references (formerly known as universal references) work with templates and std::forward to provide nice optimal handling of objects with move semantics.

In C++14 the ability to handle lambda captures with move semantics was added with generalized lambda capture; another piece of the puzzle as far as move-semantic completeness goes.

Another lesser-known but important piece of the complete picture is that class member functions can have reference qualifiers. (You can find this in the standard in section 9.3.1 [class.mfct.non-static]). This means that just as we write member functions with const modifiers, we can overload member functions with & or && modifiers and they will be called according to the value category of this. So you know when you can call std::move on data members safely.

Now, lambdas are conceptually like anonymous classes where the body is the operator() and the captured variables are the data members. And we can write lambdas with a mutable modifier indicating that the data members are mutable. (By far the common case is for them to be const, so the ordinary usage of const on a member function is inverted for lambdas.)

I said conceptually because it turns out they aren’t completely like that in at least one important way: lambdas can’t have reference qualifiers. Maybe for good reason – how would the compiler implement that? How would the programmer who wanted that behaviour specify the overloads (s)he’s wanting? These are tricky questions to answer well. But it is a problem – as far as I can tell so far, there is no way to know, inside a lambda, what the value category of the lambda object is. So the performance promise of the move semantic model falls down in the face of safety concerns: I don’t know whether I can safely move from a captured variable inside a lambda.

If anyone has any ideas about this, please let me know. Google and StackOverflow can tell me all about how move captures work with lambdas, but nothing about how to move things out of lambdas safely, or divine the value category of a lambda object. All the things I’ve tried have either not worked, or have resulted in suboptimalities of various kinds. (And frankly, if anything had worked, at this point I’d put it down to a compiler quirk and not to be relied on.)

As far as I can tell, it’s a definite shortcoming of C++ that there’s no way to do this in a compile-time, type-inferred, lambda-land way. I don’t see this in the core language issues – is it a known problem, or is there a known way to solve it that I don’t know yet?

3 comments

  1. We chatted about this already, but in case it spurs any other discussion…

    As i understand it after chatting, the problem is that you want to overload the () operator on the lambda, to be able to give an && version etc, but lambdas only let you specify a single chunk of code to associate with them.

    You could definitely make an old style functor that overloaded the () operator with different versions, and you might be able to hide away the details and make a nice declaration syntax with some macros. This is problematic though as I don’t think it would play nice with std::function, which doesn’t have that overloading exposed on it’s surface, so seems like you’d lose those overloads.

    I feel like it should be possible to make some construct similar to std::function that let you specify more than one lambda to live inside of it, for different operator () overloads, and since those overloads were exposed at the surface of the std::function type object, that it should be able to call the right lambda underneath.

    Doing it at the std::function level leaves a bit to be desired though. It seems like it’s place ought to be deeper, as a language construct. If so, the regular lambda we are used to now could be a special case of that more general object that allowed you to specify overloads.

    It’d be interesting to see the avenues you’ve attempted, even if they ended up being dead ends (:

  2. Well I tried this again last night, and it seems like it might work after all. I’m not sure – maybe I ran into a compiler issue before or maybe it’s just user error.

    The piece I missed perhaps was that the lambda has to be mutable, or an rvalue-reference doesn’t make much sense.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.