Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 5 of 8

Some parameters have an obvious usual value. Default arguments let callers omit them:

#include <iostream>
#include <string>

void greet(const std::string& name, const std::string& greeting = "Hello") {
    std::cout << greeting << ", " << name << "!\n";
}

int main() {
    greet("Ada");
    greet("Grace", "Welcome");
    return 0;
}
Hello, Ada!
Welcome, Grace!

One function, two ways to call it. Without defaults you'd write two overloads (lesson 5.6) that differ only in supplying a constant.

Defaults must come last

Arguments are matched by position, so an omitted parameter can only ever be at the end:

// WILL NOT COMPILE
void broken(int a = 1, int b) {
    (void)a;
    (void)b;
}

error: default argument missing for parameter 2 of ...

Otherwise broken(5) would be ambiguous — is 5 the a or the b? Once a parameter has a default, every parameter after it must have one too:

#include <iostream>

void configure(int width, int height = 100, bool visible = true) {
    std::cout << width << "x" << height << " visible=" << std::boolalpha << visible << "\n";
}

int main() {
    configure(50);
    configure(50, 200);
    configure(50, 200, false);
    return 0;
}
50x100 visible=true
50x200 visible=true
50x200 visible=false

You can't skip a middle one. configure(50, , false) isn't valid C++, and there's no configure(50, visible: false) syntax either — C++ has no named arguments. If you find yourself wanting to skip, that's a sign the function has too many parameters; bundle them into a struct instead (Module 9).

Declare the default once

When a function is declared and defined separately — which is every header file, lesson 5.8 — the default goes in the declaration only:

// in the header
void greet(const std::string& name, const std::string& greeting = "Hello");

// in the .cpp file
void greet(const std::string& name, const std::string& greeting) {
    // no default repeated here
}

Repeating it in both places is an error. The reason is practical: the default has to be visible to callers, and callers see only the header.

Defaults are evaluated at the call site

This matters more than it first appears. The default expression is inserted into each call, not computed once when the function is defined:

#include <iostream>

int next_id() {
    static int counter = 0;
    return ++counter;
}

void record(int id = next_id()) {
    std::cout << "recorded " << id << "\n";
}

int main() {
    record();
    record();
    record(99);
    record();
    return 0;
}
recorded 1
recorded 2
recorded 99
recorded 3

Each omitted call runs next_id() afresh — so the counter advances. Note the third call supplies 99 explicitly, so next_id() is never called for it and the counter doesn't move: the fourth call gets 3, not 4. The default is part of the call, not part of the function.

(static inside a function means the variable is created once and survives between calls, rather than being recreated each time. It's a useful escape hatch and a hazard with threads — Module 20.)

Python programmers should note this is the opposite of Python's famous mutable-default trap, where the default is evaluated once at definition. C++ re-evaluates every time, which is usually what you'd want.

Defaults versus overloads

Both let one name serve several call shapes. Choose by asking whether the behaviour differs:

Default argument — same logic, one value varies:

double round_to(double value, int places = 2);

Overload — genuinely different work:

void draw(const Circle& c);
void draw(const Square& s);

Defaults keep one implementation, so there's no risk of two versions drifting apart. Prefer them when they fit.

A caution for library authors

A default argument is baked into the caller's compiled code. If you change the default and recompile only your library, code compiled against the old header keeps the old value until it's recompiled too.

Irrelevant for a program you build all at once; a real problem for a shared library with outside users. Lesson 17.8 revisits it.

Try it

Write void print_separator(int length = 20, char symbol = '-') that prints length copies of symbol followed by a newline. Call it three ways: no arguments, length only, and both.

Checkpoint

  • A default argument makes a parameter optional: void f(int a, int b = 10);
  • Defaults must occupy a contiguous run at the end of the parameter list.
  • C++ has no named arguments, so you can't skip a middle parameter.
  • State the default in the declaration only, never in both declaration and definition.
  • The default expression is evaluated at each call, unlike Python.
  • Use defaults when behaviour is identical; use overloads when it genuinely differs.

Questions about this lesson

Log in to ask a question.