Lambda expressions solve the problem of writing small, temporary functions without creating a separate named function every time.

In C++, a lambda expression is useful when you need logic “right here” for a short operation.

Example:

auto add = [](int a, int b) {
return a + b;
};

int result = add(2, 3); // 5

Instead of writing:

int add(int a, int b) {
return a + b;
}

Problems lambda expressions solve

1. Avoid creating many small named functions

Without lambda:

bool isEven(int n) {
return n % 2 == 0;
}

With lambda:

auto isEven = [](int n) {
return n % 2 == 0;
};

Useful when the function is only needed in one place.


2. Cleaner code with algorithms

Example: sorting numbers in descending order.

Without lambda, you may need a separate comparison function.

With lambda:

std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
return a > b;
});

This keeps the sorting rule close to the sort call.


3. Passing behavior as an argument

Many C++ functions accept “what to do” as a parameter.

Example:

std::for_each(numbers.begin(), numbers.end(), [](int n) {
std::cout << n << std::endl;
});

The lambda tells for_each what action to perform for each number.


4. Accessing local variables using capture

A lambda can use variables from the surrounding function.

int limit = 10;

auto greaterThanLimit = [limit](int value) {
return value > limit;
};

Here, [limit] means the lambda captures the local variable limit.

This is one of the biggest advantages of lambdas.


5. Reducing boilerplate code

Instead of writing long class/function objects, lambdas make code shorter.

Before lambdas, C++ often used function objects:

struct Compare {
bool operator()(int a, int b) {
return a > b;
}
};

With lambda:

[](int a, int b) {
return a > b;
}

Much simpler.


Simple way to remember

A lambda expression is used when you want to say:

“Here is a small function I need only at this place.”

Common use cases:

std::sort()
std::find_if()
std::for_each()
callbacks
filters
custom comparisons
event handlers

For example:

std::vector<int> nums = {1, 2, 3, 4, 5};

auto it = std::find_if(nums.begin(), nums.end(), [](int n) {
return n > 3;
});

This finds the first number greater than 3.

So lambda expressions mainly solve code clarity, less boilerplate, and easy passing of small logic into functions.

Here is your code rewritten without lambda expressions.

Main changes:

  1. Replaced this lambda:
threads.emplace_back([this] { ... });

with a normal member function:

threads.emplace_back(&threadpool::workerThread, this);
  1. Replaced this lambda:
cv.wait(lock, [this]{ return stop || !tasks.empty(); });

with a normal while loop:

while (!stop && tasks.empty()) {
cv.wait(lock);
}
  1. Added missing header:
#include <limits>
  1. Enabled cv.notify_one(); in enqueue(). Without this, worker threads may keep waiting and not execute tasks.
#include <iostream>
#include <functional>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <limits>

using namespace std;

class threadpool
{
vector<thread> threads;
queue<function<void()>> tasks;
mutex mtx;
condition_variable cv;
bool stop;

void workerThread()
{
while (true)
{
function<void()> task;

{
unique_lock<mutex> lock(mtx);

while (!stop && tasks.empty())
{
cv.wait(lock);
}

if (stop && tasks.empty())
{
return;
}

task = move(tasks.front());
tasks.pop();
}

task();
}
}

public:
threadpool(size_t num_threads = thread::hardware_concurrency())
{
stop = false;

for (size_t i = 0; i < num_threads; i++)
{
threads.emplace_back(&threadpool::workerThread, this);
}
}

template<class F, class... Args>
void enqueue(F&& f, Args&&... args)
{
{
unique_lock<mutex> lock(mtx);
tasks.emplace(bind(forward<F>(f), forward<Args>(args)...));
}

cv.notify_one();
}

~threadpool()
{
{
unique_lock<mutex> lock(mtx);
stop = true;
}

cv.notify_all();

for (thread &worker : threads)
{
worker.join();
}
}
};

void function1()
{
for (int i = 0; i < 10; i++)
{
cout << "This is a function f1: " << i << endl;
}
}

void function2()
{
for (int i = 0; i < 10; i++)
{
cout << "This is a function f2: " << i << endl;
}
}

int main()
{
threadpool pool(4);

while (true)
{
int i;

cout << "\nSelect task to execute:\n";
cout << "1. Execute function1\n";
cout << "2. Execute function2\n";
cout << "3. Exit\n";
cout << "Enter choice: ";

if (!(cin >> i))
{
cout << "Invalid input. Please enter a number.\n";
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
continue;
}

switch (i)
{
case 1:
pool.enqueue(function1);
break;

case 2:
pool.enqueue(function2);
break;

case 3:
cout << "Exiting...\n";
return 0;

default:
cout << "Invalid input. Please enter 1, 2, or 3.\n";
break;
}
}
}

The most important replacement is this:

threads.emplace_back(&threadpool::workerThread, this);

It means:

Start a new thread and run the workerThread() member function on the current threadpool object.