C/C++ Arena

Step 3 of 5

const references

Passing a std::string, a vector, or any large object by value copies the whole thing: every character, every element. For a function that only reads its argument, that copy is wasted work. Passing by reference avoids it, and adding const promises (and enforces) that the function won't modify the argument:

#include <iostream>
#include <string>

bool ends_with(const std::string& s, const std::string& suffix) {
    if (suffix.size() > s.size()) {
        return false;
    }
    size_t start = s.size() - suffix.size();
    for (size_t i = 0; i < suffix.size(); i++) {
        if (s[start + i] != suffix[i]) {
            return false;
        }
    }
    return true;
}

int main() {
    std::cout << ends_with("report.pdf", ".pdf") << ends_with("a.txt", ".pdf") << ends_with("x", "long") << "\n";
}
100

The everyday rule

A const& parameter can also bind to temporaries and literals: ends_with("report.pdf", ".pdf") works because C++ creates temporary strings for the call. A non-const & can't bind to a temporary.

Size checks first

ends_with checks lengths before indexing. s.size() - suffix.size() on unsigned sizes would wrap around to a huge number if the suffix were longer, and the loop would read out of bounds. Guard clauses like this prevent exactly the kind of bug you saw in the integer types module.

Your turn: write bool starts_with(const std::string& s, const std::string& prefix) without using any library starts_with.

Previous: Pass by reference Next: Overloading