Background
As an organizational strategy, I like to define function-local lambdas in complicated functions. It's good for encapsulating multi-step logic, repeated operations, etc. (the sorts of things that functions are good for in general), but without creating something that'll be visible outside of the scope where it's used. It's kind of a synthesis of/alternative to the styles John Carmack lays out in his essay on the merits of inlining code in that it keeps everything neatly bottled up in the function it's intended to be used in while also giving a (compiler-recognized) name to document each block of functionality. A simple, contrived example might look like this (just pretend there was actually something complex enough going on here to merit using this sort of style):
void printSomeNumbers(void)
{
const auto printNumber = [](auto number) {
std::cout << number << std::endl; // Non-trivial logic (maybe formatting) would go here
};
printNumber(1);
printNumber(2.0);
}
Semantically speaking, the compiled form of this function is 'supposed' to create an instance of an implicitly-defined functor, then call operator()()
on that functor for each of the provided inputs, since that's what it means to use a lambda in C++. In optimized builds, though, the as-if rule frees the compiler up to inline some stuff, meaning that the actual generated code is probably going to just inline the contents of the lambda and skip defining/instantiating the functor entirely. Discussions of this sort of inlining have come up in past discussions here and here, among other places.
Question
In all of the lambda inlining questions and answers I've found, the presented examples haven't made use of any form of lambda capture, and they also largely pertain to passing a lambda as a parameter to something (i.e. inlining a lambda in the context of an std::for_each
call). My question, then, is this: can a compiler still inline a lambda which captures values? More specifically (since I'd assume that the lifetimes of the various variables involved factors into the answer quite a bit), can the compiler reasonably inline a lambda which is only used inside of the function where it's defined, even if it captures some things (i.e. local variables) by reference?
My intuition here would be that inlining should be possible, since the compiler has full visibility into all of the code and the relevant variables (including their lifetimes relative to the lambda), but I'm not positive and my assembly-reading skills aren't up to snuff enough to get a reliable answer for myself.
Additional Example
Just in case the specific use-case I'm describing isn't quite clear, here's a modified version of the lambda above which makes use of the kind of pattern I'm describing (again, please ignore the fact that the code is contrived and needlessly over-complicated):
void printSomeNumbers(void)
{
std::ostringstream ss;
const auto appendNumber = [&ss](auto number) {
ss << number << std::endl; // Pretend this is something non-trivial
};
appendNumber(1);
appendNumber(2.0);
std::cout << ss.str();
}
I'd expect that an optimizing compiler should have enough information to completely inline all lambda usages and not generate (or at least not keep) any functors here, even though it's making use of a captured-by-reference variable that 'should' be treated as a member of some auto-generated closure type.