Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 4 of 8

Lesson 5.3 gave you two options with an awkward gap between them: copying is safe but can be slow, and references are fast but let the function change your data. const& is both.

#include <iostream>
#include <string>

void print_length(const std::string& text) {
    std::cout << text.size() << "\n";
}

int main() {
    std::string message = "Evil Genius Society";
    print_length(message);
    return 0;
}
19

Read the parameter right to left: a reference to a constant string. No copy is made, and the function cannot modify it.

What const buys you here

It's a promise to the caller. Seeing const& in a declaration, you know your object is safe. That means a plain & becomes meaningful — it now reliably signals "this will be modified," which fixes the readability problem from lesson 5.3.

It's enforced. Not a convention — the compiler rejects violations:

// WILL NOT COMPILE
#include <string>

void print_length(const std::string& text) {
    text += "!";        // can't modify through a const reference
}

error: passing 'const std::string' as 'this' argument discards qualifiers

It accepts temporaries. Unlike T&:

#include <iostream>
#include <string>

void show(const std::string& text) {
    std::cout << "[" << text << "]\n";
}

int main() {
    std::string named = "variable";
    show(named);
    show("literal");              // a temporary — fine for const&
    show(named + " plus more");   // also a temporary
    return 0;
}
[variable]
[literal]
[variable plus more]

show("literal") builds a temporary std::string and binds the const& to it. With a plain std::string& none of the last two calls would compile. This alone makes const& far more usable.

The rule

Pass by const& for anything larger than a pointer; pass small built-in types by value.

In practice:

| Type | Pass as | Why | | --- | --- | --- | | int, char, bool, double | by value | copying is one instruction; a reference is indirection for nothing | | std::string | const& | copying allocates memory | | std::vector<T> | const& | copying duplicates every element | | your own classes | const& | usually cheaper, and safe |

The cutoff is roughly "bigger than 8–16 bytes, or owns memory." int by const& isn't wrong, it's just pointless — a reference is typically a pointer under the hood, so you'd pay indirection to avoid copying something the same size as a pointer.

const-correctness spreads

Once you mark a parameter const, everything it touches must also be const-friendly. That sounds like a burden and turns out to be a feature:

#include <iostream>
#include <string>

std::size_t count_vowels(const std::string& text) {
    std::size_t count = 0;
    for (char c : text) {
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
            ++count;
        }
    }
    return count;
}

int main() {
    std::cout << count_vowels("evil genius society") << "\n";
    return 0;
}
8

Reading from a const reference is fine — you just can't write. If a function needs to modify its argument, that's a design decision you now have to make deliberately, rather than something that happens by accident.

You'll meet this again as const member functions in Module 9, and as an API design principle in lesson 19.6.

When a copy is actually right

const& isn't universal. Take a copy when the function needs its own version to modify:

#include <iostream>
#include <string>

std::string shouted(std::string text) {     // by value on purpose
    for (char& c : text) {
        if (c >= 'a' && c <= 'z') {
            c = static_cast<char>(c - ('a' - 'A'));
        }
    }
    return text;
}

int main() {
    std::string quiet = "evil genius";
    std::cout << shouted(quiet) << "\n";
    std::cout << quiet << "\n";
    return 0;
}
EVIL GENIUS
evil genius

The function needs a modifiable string to build its result. Taking the parameter by value gives it one directly — cleaner than taking const& and then copying inside, and it lets the compiler skip the copy entirely when you pass a temporary.

Try it

Write bool is_palindrome(const std::string& text) that reports whether a string reads the same backwards, comparing characters exactly (no case folding). Test it with "racecar", "hello", and "".

Checkpoint

  • const T& passes by reference (no copy) while forbidding modification.
  • It makes a plain T& a reliable signal that the argument will be changed.
  • Unlike T&, a const& binds to temporaries and literals.
  • Default to const& for strings, containers, and your own classes; pass built-ins by value.
  • Take a parameter by value when the function needs its own modifiable copy.

Questions about this lesson

Log in to ask a question.