Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a small project the other day—a simple top-down shooter—and I ran into a classic C++ annoyance. I had a list of enemies in a std::vector, and I wanted to remove all the ones that had their health drop to zero. Now, the "correct" way to do this in C++ is the erase-remove idiom using std::remove_if.

The Boilerplate Burden

At first, I did what I always did: I wrote a separate predicate function to handle the logic. It looked something like this:

struct Enemy {
    std::string name;
    float health;
};

bool isDead(const Enemy& e) {
    return e.health <= 0.0f;
}

// ... inside main ...
std::vector<Enemy> enemies = { {"Goblin", 10.0f}, {"Orc", 0.0f}, {"Troll", 5.0f} };
enemies.erase(std::remove_if(enemies.begin(), enemies.end(), isDead), enemies.end());

It works, sure. But as I looked at my code, I realized I was defining a function in the global namespace (or as a static member) that is used exactly once in the entire program. It's a bit of clutter. I found myself thinking, "Why can't I just put the logic right where I'm calling the function?"

Moving the Logic Inline

This is where lambda expressions come in. I decided to scrap the isDead function and try the lambda syntax. A lambda is essentially an anonymous function you define on the fly. I replaced isDead with this block:

enemies.erase(
    std::remove_if(enemies.begin(), enemies.end(), [](const Enemy& e) {
        return e.health <= 0.0f;
    }), 
    enemies.end()
);

If you've seen these before, the [] is the "capture clause," the (const Enemy& e) is the parameter list, and the {...} is the body. It's much cleaner. The logic is now physically located where it's being used, so I don't have to scroll up fifty lines to remember what isDead actually does.

The "Scope Wall" Problem

Now, here is where things got interesting. I decided I didn't want a hardcoded 0.0f. I wanted a variable, minHealth, that I could tweak during gameplay to simulate a "mercy" mechanic where enemies only die if they hit a specific threshold.

I tried this:

float minHealth = -5.0f;
enemies.erase(
    std::remove_if(enemies.begin(), enemies.end(), [](const Enemy& e) {
        return e.health <= minHealth; // Error!
    }), 
    enemies.end()
);

The compiler immediately complained. It told me minHealth wasn't captured. This is the "scope wall." A lambda is its own little island; it can't see the local variables of the function it's sitting in unless you explicitly invite them in via that [] bracket.

Breaking Through with Captures

To fix this, I had to tell the lambda to "capture" minHealth. I tried adding it to the brackets:

enemies.erase(
    std::remove_if(enemies.begin(), enemies.end(), [minHealth](const Enemy& e) {
        return e.health <= minHealth;
    }), 
    enemies.end()
);

That worked. By putting minHealth in the brackets, I created a copy of that variable inside the lambda. But what if I wanted the lambda to actually change the variable? Or what if I had ten different variables and didn't want to list them all?

I experimented with two shorthand options. Using [=] tells C++, "capture everything I use by value (make copies)." Using [&] tells it, "capture everything I use by reference."

I'll give you a word of caution here: [&] is incredibly convenient, but it's dangerous if the lambda outlives the variables it captures (like if you pass the lambda to another thread or store it in a class). In this case, since std::remove_if executes immediately, [&] is perfectly safe and slightly more performant since we aren't copying data.

My final, polished version ended up looking like this:

float minHealth = -5.0f;
enemies.erase(
    std::remove_if(enemies.begin(), enemies.end(), [&](const Enemy& e) {
        return e.health <= minHealth;
    }), 
    enemies.end()
);

It's concise, it's local, and it's powerful. We've turned a multi-line boilerplate process into a single, readable expression.




📋 Practical Task

Implementing a Custom Sort for a High-Score Leaderboard

You are building a leaderboard for a game. You have a struct Player with a std::string name and an int score. You have a std::vector<Player> containing several players.

Your task is to use std::sort from the <algorithm> header and a lambda expression to sort the players based on the following rules:

  • Players should be sorted by score in descending order (highest score first).
  • If two players have the same score, they should be sorted by name in alphabetical order.

Write a small program that initializes a vector with at least four players (including at least two with the same score) and prints the sorted list to the console to verify your lambda logic works correctly.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.