Skip to main content

Default Function Arguments

Default arguments allow function parameters to have default values when not explicitly provided by the caller.

Basic Syntax

// Declaration with defaults
void print(std::string msg, int count = 1, bool newline = true);

// Calls
print("Hello"); // count=1, newline=true
print("Hello", 3); // count=3, newline=true
print("Hello", 3, false); // count=3, newline=false
info

Default arguments must appear rightmost in the parameter list. Once a parameter has a default, all following parameters must also have defaults.

Rules and Restrictions

// CORRECT: defaults are rightmost
void f1(int a, int b = 2, int c = 3);

// WRONG: non-default after default
// void f2(int a = 1, int b, int c = 3); // Error!

// CORRECT: multiple defaults
void f3(int a, int b = 2, int c = 3, int d = 4);

Declaration vs Definition

// In header (.h)
void configure(int port, std::string host = "localhost", int timeout = 30);

// In source (.cpp) - NO defaults here!
void configure(int port, std::string host, int timeout) {
// Implementation
}

// WRONG: defaults in both places
// void configure(int port, std::string host = "localhost", int timeout = 30) {
// // Error!
// }
warning

Default arguments should only appear in the declaration, not the definition (if separated).

Common Patterns

Pattern 1: Optional Configuration

#include <string>

class Logger {
public:
void log(const std::string& msg,
const std::string& level = "INFO",
bool timestamp = true);
};

// Usage
logger.log("Server started"); // INFO, with timestamp
logger.log("Error occurred", "ERROR"); // ERROR, with timestamp
logger.log("Debug info", "DEBUG", false); // DEBUG, no timestamp

Pattern 2: Builder-Style APIs

class HttpRequest {
public:
HttpRequest& setMethod(const std::string& method = "GET");
HttpRequest& setTimeout(int seconds = 30);
HttpRequest& setRetries(int count = 3);
};

// Usage
request.setMethod() // Uses "GET"
.setTimeout() // Uses 30
.setRetries(5); // Override to 5

Pattern 3: Backward Compatibility

// Old API
void process(int value);

// New API - adds optional parameter
void process(int value, bool verbose = false);

// Old code still works!
process(42); // verbose=false (backward compatible)

Non-Constant Defaults

Default arguments are evaluated at call time, not declaration time:

int getDefaultValue() {
return 42;
}

void func(int x = getDefaultValue()) {
std::cout << x << '\n';
}

// Each call evaluates getDefaultValue()
func(); // Calls getDefaultValue()
func(); // Calls getDefaultValue() again

Member Variables as Defaults

class Widget {
int defaultSize_ = 100;

public:
// WRONG: can't use member as default
// void resize(int size = defaultSize_); // Error!

// CORRECT: use overload or other approach
void resize(int size);
void resize() { resize(defaultSize_); }
};

Default Arguments vs Overloading

// Approach 1: Default arguments
void display(std::string text, int size = 12, bool bold = false);

// Approach 2: Overloading
void display(std::string text);
void display(std::string text, int size);
void display(std::string text, int size, bool bold);
Default ArgumentsOverloading
Single functionMultiple functions
Can't change logic per parameterDifferent logic per version
Evaluated at call siteCan have different implementations
Less codeMore flexibility

Practical Examples

Example 1: Database Connection

#include <string>

class Database {
public:
bool connect(
const std::string& host = "localhost",
int port = 5432,
const std::string& user = "admin",
const std::string& password = "",
int timeout = 30
);
};

// Usage examples
db.connect(); // All defaults
db.connect("192.168.1.100"); // Custom host
db.connect("192.168.1.100", 3306); // Custom host and port
db.connect("192.168.1.100", 3306, "root", "pwd"); // Custom connection

Example 2: File Operations

#include <string>
#include <fstream>

class FileWriter {
public:
void write(
const std::string& filename,
const std::string& content,
bool append = false,
bool createIfMissing = true
) {
auto mode = std::ios::out;
if (append) mode |= std::ios::app;
if (!createIfMissing) mode |= std::ios::in;

std::ofstream file(filename, mode);
file << content;
}
};

Best Practices

success

DO:

  • Use defaults for frequently-used values
  • Place defaults in header files (declarations)
  • Keep defaults simple (literals or simple expressions)
  • Use defaults for optional configuration
danger

DON'T:

  • Use defaults with complex expressions (performance)
  • Mix defaults and overloading carelessly
  • Use member variables as default values
  • Put defaults in both declaration and definition