Lambda Expressions in C++: When Do They Really Speed Up Code?
\n\nHello, colleague! If you've ever browsed Stack Overflow, you've likely seen a question that has gathered thousands of views: "What is a lambda expression, and when should I use one?". And this is no coincidence. Lambdas in C++ are a topic that both simplifies life and generates a lot of debate about optimization and performance.
\n\nToday, we won't just give a dry definition. We'll break down how lambda expressions work under the hood, when their use makes code faster, and when, on the contrary, it creates hidden problems. Let's go!
\n\nWhat is a Lambda Expression? An Inside Look
\n\nFormally, a lambda expression is an anonymous function that can be defined right at the call site. But if you dig deeper, the compiler turns each lambda into a unique function object (functor). Here's an example:
\n\n// Lambda\nauto add = [](int a, int b) { return a + b; };\n\n// What it turns into (simplified)\nclass __lambda_1 {\npublic:\n auto operator()(int a, int b) const { return a + b; }\n};\n__lambda_1 add;\n\nThe key point: each lambda has its own unique type. This means the compiler can aggressively inline calls — unlike function pointers or std::function.
Performance: Where Do Lambdas Win?
\n\nLet's get straight to the point. Here are three scenarios where lambdas provide a noticeable performance boost:
\n\n1. Inlining and No Indirect Calls
\n\nWhen you pass a lambda to a template function (e.g., std::sort), the compiler knows the exact type. This allows it to inline the lambda's code directly into the calling function. Compare:
// Option 1: function pointer (slower)\nbool compare(int a, int b) { return a < b; }\nstd::sort(vec.begin(), vec.end(), compare);\n\n// Option 2: lambda (faster)\nstd::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; });\n\nIn the first case — an indirect call via a pointer. In the second — direct inlining. On large data volumes (millions of elements), the difference can reach 20-30% in time.
\n\n2. Capturing Variables Without Unnecessary Copies
\n\nLambdas allow flexible capture management. The right choice between [=], [&], and specific variables directly affects performance:
std::vector<std::string> data;\nstd::string prefix = "prefix_";\n\n// Bad: copying a heavy string\nstd::for_each(data.begin(), data.end(), \n [=](std::string& s) { s = prefix + s; });\n\n// Good: capture by reference\nstd::for_each(data.begin(), data.end(), \n [&](std::string& s) { s = prefix + s; });\n\nCapture by value ([=]) creates a copy of prefix inside the functor. For std::string, this can be expensive. Capture by reference ([&]) is lightweight.
3. Lambdas in STL Algorithms
\n\nModern STL algorithms (C++17/20) actively use lambdas for parallel computations. For example, std::transform with the execution policy std::par:
std::vector<int> input(1000000);\nstd::vector<int> output(input.size());\n\n// Parallel processing\nstd::transform(std::execution::par, \n input.begin(), input.end(), output.begin(),\n [](int x) { return x * x + 2 * x + 1; });\n\nThe lambda here is an ideal candidate for parallelization, as it has no side effects (pure function). The compiler and library can safely run it on multiple threads.
\n\nWhen Can Lambdas Harm Performance?
\n\nIt wouldn't be fair to only talk about the pros. There are situations where lambdas are not the best choice:
\n\n1. Heavy Capture by Value
\n\nIf you capt