Hello, and welcome to C++ Weekly. I'm your host, Jason Turner. In this episode
I'm gonna kind of, maybe, backpedal just a little bit on a statement that I made in one
of my most recent episodes about lambdas when I said that it's not possible to put a lambda
in a container. There is a way to do that, it's just ugly, but you should stay tuned for it.
And of course, I am available for code reviews, contracting, on-site training.
You should contact me about those things, and you should check out the links
in the video description to the puzzle books and the Best Practices books and upcoming events that
I am going to be speaking at and putting on and all of those things. I said that there are ways
to put lambdas in containers but it's a little bit ugly. That's mostly correct. There's actually
three different ways that you might put lambdas in containers, and um, well, here.
Let's just say I want to create a vector of lambdas.
So I need to figure out what the type of this vector would be, and then I need to figure out
how to actually put that in there. Now a funny thing about lambdas in C++
is that we're not allowed to actually know the type of the lambda, although we could do this:
We can say, "Give me the decltype of that lambda and make a vector of it." So what is the type
of the lambda? I want a vector of that thing, and then I could go and do a push_back of this lambda
"l". But what does that gain me? Why would I want to have multiple copies of the exact same lambda?
Now interestingly, just to be clear, this does work with some sort of capture as well.
All right, so this works like this, but it doesn't really gain us anything. Or does it?
Does it gain us something? Maybe it gains us something. We might be able to do something like a
pre-increment of i and add that to j, and then if we've got a mutable lambda so that this compiles,
and then let's say I want to call l. So that just incremented the i, and then I make a copy of it
to add that here, um, that's plausible... and keep some sort of running count of the
current copy of the lambda? I'm having a hard time actually coming up with a use case here,
but that's weird and difficult to use. So let's go ahead and say this is the case that the average
programmer is going to look at. We know that we want a function that returns an int and takes an
int, and then we try to coerce this lambda to pass it into it, and this works and makes considerably
more sense because I can actually push_back that lambda and I can push_back an entirely new lambda.
Uh, let's just make this an identity function or something - I don't know, just have it return the
exact same thing again; but if you watched those previous episodes on std::function
and lambdas then we know that this is like, an awful lot of overhead, and we can just kind of
do a hand wavy scroll through and say, "Wow! Oh no! Look at all of the code that was generated,
isn't that terrible!" In the case (and again, if you watched that previous
episode) in the case that we don't have a capture, this thing is implicitly convertible to
a function pointer. This one below here is also convertible implicitly to a function pointer and
we can declare our vector as a vector of function pointers
that take ints and return ints, and that works; and it's going to be considerably
less overhead because we don't have all this compile time and run time type erasure
overhead that we get from using std::function; but then we've just limited ourselves
to not being able to have a lambda with a capture. Uh, okay. So can we do something
slightly more useful? Let's go ahead and go back to this decltype version.
So that's the decltype of l. We're not allowed to know its type. We can't push_back another
lambda that takes an int and returns an int even if we're explicit about it here
because they're different types. It's (they're) required to be different types, and we might even
see this here. So this is "main auto lambda six", whatever. Uh, "no known conversion
from (this is going to be fun) uh, from main()::
to ." Okay. Let's make this another...let's make this an int 1.
Okay. So yeah, "no known conversion from main():: to basically main()::<lambda
(int)>." That's incredibly helpful. Thank you, GCC. That is exactly the kind of feedback that
I needed from you. So let's just see if Clang gives us a slightly better error here. So, okay,
this one tells us "cannot convert from type, uh, no known conversion from lambda source at line 11
to lambda source at line six." Okay. That's at least something. We now know that these are
two different types of lambdas even though they don't really look different to us, and as we saw
previously we're able to do it with a function pointer with absolutely no problem whatsoever.
Okay, how do we do the magic? There's a way to do this in fact. What we can do is create a function
that takes some values - let's just take, I don't know, some "value," and we're going to return a
lambda that captures that value, takes an int, and always returns i plus that value. So this
is a well-known lambda type. There's going to be exactly one type defined here that is whatever
the name of this thing is. I'm not allowed to name it, but it is this auto return type,
so this is all valid C++14. So now if I were to come in here and I were to say, "Create a vector
of the decltype of the result of calling this 'make_lambda' function with some value." So I just
need to pretend like I'm calling that function with some value and say, "Give me the, give me
the type of that." That's, that's what I want. I want the type of that. Now if I call 'make_lambda'
with the value 1 and I call it again with the value 2
then I, I can do this, and look at that. The compiler's actually able to optimize away
the entire thing. Now that's Clang, and it's Clang's heap elision with standard vector
coming into play. Let's go ahead and move it back to GCC just so we have a little bit more
of a stable question here. Uh, yeah, I mean, you know, it's approximately, I'm just again
hand waving here, approximately the amount of code that was generated when we had a vector
of function pointers. That is at least mildly interesting. It's also pretty dangerous actually.
So if I wanted to create a function, let's call it 'make_vector_lambdas'...
So this is just a little helper function for us
but this code has a problem because it relies on the type of a lambda
that is defined inside this compilation unit. It would, shall we say, be
subject to ODR problems. Um, each compilation unit is going to be getting its own type of vector that
is based on its own lambda type. 99% sure that's correct. Pretty positive this is not a good idea,
particularly if you're using any sort of public header,
but it's interesting. A probably smarter idea would be to simply create your own type.
Now we can get the exact same behavior
but without any questions of ODR. Relatively simply defined code and re-usability if we
wanted it, and this becomes a little bit unnecessarily complicated down here as well.
So just an aside that it's relatively easy to create your own callable if you actually wanted
to do something like this, but it is in fact technically possible to create a vector of lambdas
if you were so inclined, and that can be done with some decltype foo with function pointers
with std::function, which now we have to question, "Is that actually a vector of lambdas?" No,
that's a vector of std::function wrapper object, so we're not going to go there;
or by creating your own callable type that's just like the C++ lambda created for you, and I've done
so many episodes on lambdas, including, I think, there's an episode on what is a lambda exactly.
Not difficult to implement yourself in this particular scenario, uh,
so there you go. Hopefully this was a fun episode. It was, you know, an interesting exploration
for me to play around with actually doing these decltypes on lambda return types from functions,
and then playing with what kind of code the compiler generated and what my options were, so
hopefully it was some fun for you as well. So be sure to subscribe and check out my other episodes.