What Are Default Arguments?

A default argument is a fallback value assigned to a function parameter. When a caller doesn't provide a value for that parameter, the default is used automatically.

Here's an example:

void display(int width, int height=25)  // 25 is the default for height
{
    std::cout << "Width: " << width << '\n';
    std::cout << "Height: " << height << '\n';
}

When calling this function, you can choose to provide the second argument or let it use the default:

#include <iostream>

void display(int width, int height=25)
{
    std::cout << "Width: " << width << '\n';
    std::cout << "Height: " << height << '\n';
}

int main()
{
    display(80, 50);  // uses caller's value: 50
    display(120);     // uses default value: 25

    return 0;
}

Output:

Width: 80
Height: 50
Width: 120
Height: 25

The first call provides both arguments explicitly. The second call omits the second argument, so the default value of 25 is used.

Note the syntax: default arguments must use the equals sign (=). Parentheses or braces don't work:

void alpha(int value = 7);    // OK
void beta(int value ( 7 ));   // Compile error
void gamma(int value { 7 });  // Compile error
Key Concept
The compiler inserts default arguments at the call site. When it sees display(120), it rewrites it as display(120, 25) before compiling.

When to Use Default Arguments

Default arguments are ideal when a parameter has a sensible default but you want to allow callers to override it when necessary.

Examples:

int simulateDiceRoll(int faces=6);
void openFile(std::string path="output.txt");

Default arguments are also valuable when adding parameters to existing functions. Adding a parameter without a default breaks all existing calls. Adding one with a default keeps existing calls working while allowing new code to specify custom values.

Multiple Default Arguments

Functions can have multiple parameters with defaults:

#include <iostream>

void configure(int speed=100, int volume=50, int brightness=75)
{
    std::cout << "Settings: " << speed << " " << volume << " " << brightness << '\n';
}

int main()
{
    configure(200, 80, 90);  // all explicit
    configure(200, 80);      // brightness defaults to 75
    configure(200);          // volume and brightness use defaults
    configure();             // all use defaults

    return 0;
}

Output:

Settings: 200 80 90
Settings: 200 80 75
Settings: 200 50 75
Settings: 100 50 75

C++ doesn't support skipping arguments (like configure(,,90)). This has important implications:

1. Arguments must be provided left-to-right

You cannot skip an argument to provide one further right:

void setup(std::string_view name="Server", double timeout=5.0);

int main()
{
    setup();              // OK: both defaulted
    setup("Database");    // OK: timeout defaults to 5.0
    setup(10.0);          // Error: cannot skip name to provide timeout

    return 0;
}

2. Parameters with defaults must be rightmost

If one parameter has a default, all parameters to its right must also have defaults:

void process(int required, int optional=10);       // OK
void invalid(int optional=10, int required);       // Error
Rule
Once a parameter has a default argument, all subsequent parameters must also have defaults.

3. Order matters

Place the parameter most likely to be customized on the left, since leftmost parameters are easier to override.

Declaration Rules

Default arguments cannot be redeclared in the same translation unit. You can declare them in either the forward declaration or the definition, but not both:

#include <iostream>

void display(int width, int height=25);  // forward declaration

void display(int width, int height=25)   // Error: redefinition
{
    std::cout << "Width: " << width << '\n';
    std::cout << "Height: " << height << '\n';
}

Default arguments must be declared before they're used:

#include <iostream>

void display(int width, int height);  // no default yet

int main()
{
    display(100);  // Error: default not visible here

    return 0;
}

void display(int width, int height=25)
{
    std::cout << "Width: " << width << '\n';
    std::cout << "Height: " << height << '\n';
}
Best Practice
Put default arguments in the forward declaration (especially in header files) rather than in the definition. This makes them visible wherever the function is used.

Example:

In graphics.h:

#pragma once

void display(int width, int height=25);

In main.cpp:

#include "graphics.h"
#include <iostream>

void display(int width, int height)
{
    std::cout << "Width: " << width << '\n';
    std::cout << "Height: " << height << '\n';
}

int main()
{
    display(100);  // Works because default is in header

    return 0;
}

Default Arguments and Overloading

Functions with default arguments can be overloaded:

#include <iostream>
#include <string_view>

void output(std::string_view text)
{
    std::cout << text << '\n';
}

void output(char character = '*')
{
    std::cout << character << '\n';
}

int main()
{
    output("Welcome");  // resolves to output(std::string_view)
    output('X');        // resolves to output(char)
    output();           // resolves to output(char) with default '*'

    return 0;
}

Default values aren't part of a function's signature, so these overloads are distinct:

void process(int value);                     // signature: process(int)
void process(int value, int count = 5);      // signature: process(int, int)
void process(int value, double scale = 2.0); // signature: process(int, double)

Ambiguity Risks

Default arguments can create ambiguous calls:

void compute(int value = 0)
{
}

void compute(double value = 0.0)
{
}

int main()
{
    compute();  // Ambiguous: compute(0) or compute(0.0)?

    return 0;
}

More complex example:

void process(int value);
void process(int value, int quantity = 10);
void process(int value, double multiplier = 1.5);

int main()
{
    process(5, 8);      // OK: resolves to process(int, int)
    process(5, 3.2);    // OK: resolves to process(int, double)
    process(5);         // Ambiguous: which overload?

    return 0;
}

The call process(5) could match process(int), process(int, int), or process(int, double). The compiler cannot choose, resulting in an error.

Best Practice
Design your overloads carefully to avoid ambiguity when using default arguments. Test that all intended call patterns compile successfully.

Summary

Default arguments provide fallback values for function parameters:

Syntax and Rules:

  • Use = to specify default values: void func(int x = 10)
  • Parameters with defaults must be rightmost
  • Cannot skip arguments; must provide left-to-right
  • Declare defaults in headers, not definitions

Best Practices:

  • Use defaults for parameters with sensible fallback values
  • Place most-likely-to-change parameters leftmost
  • Be careful combining defaults with overloading to avoid ambiguity

Declaration Rules:

  • Default can appear in declaration OR definition, not both
  • Must be visible before use (put in header files)
  • Not part of function signature for overload differentiation