06 - default arguments

Sometimes you may encounter a situation where multiple overloads are written to supply predefined values:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void start_timer(long precision, long delay) // A
{
	// [...]
}

void start_timer(long precision) // B
{
	start_timer(precision, 0); // calls overload A
}

void start_timer() // C
{
	start_timer(1000); // calls overload B
}

You can use default arguments to simplify this code:

1
2
3
4
void start_timer(long precision = 1000, long delay = 0)
{
	// [...]
}

In vast majority of cases, default arguments are temporary objects, often literals. But you can also do stuff like void f(int n = g());.

How it works exactly? Very simple: when a function is called with reduced amount of arguments, the call behaves as if default arguments were written in place of ommited arguments. This means that each time f is called without the argument, g will be called to supply it, as if the code was f(g()). Formally speaking, default argumnets are evaluated every time the function is called.

There is no support for calling functions like func(x,,z). Only rightmost arguments can be ommited and only rightmost parameters can have default arguments specified:

1
2
3
4
5
6
void func1(int a,     int b,     int c = 10); // ok
void func2(int a,     int b = 5, int c = 10); // ok
void func3(int a = 0, int b = 5, int c = 10); // ok
void func4(int a = 0, int b,     int c);      // ill-formed
void func5(int a = 0, int b = 5, int c);      // ill-formed
void func6(int a = 0, int b,     int c = 10); // ill-formed

Default arguments act as additional overloads so it's possible to create overload sets which can have ambiguities:

1
2
3
4
int f(int);
int f(int, int = 1); // valid, but impossible to call with 1 argument

f(1); // error: ambiguous call

Declarations

If a function has separate declaration and definition, default arguments should be present only in the declaration.

In practice

Arguments for default arguments (pun intended):

  • Less code compared to overloading.

  • Refactoring and extensibility: it's possible to add parameters to a function and as long as new parameters have default values specified, no changes are needed for existing code (obviously the default argument should preserve behavior that was before the change).

Arguments against default arguments:

  • They create code that is hard or impossible to reuse (particulary problematic if inheritance or function pointers are involved). There is no way to "extract" default arguments to use them elsewhere.

  • They do not allow certain usage patterns, particulary default arguments can not refer to other arguments:

    1
    2
    3
    4
    5
    6
    7
    
    void draw_rectangle(int width, int height = width); // ill-formed
    
    void draw_rectangle(int width, int height);
    void draw_rectangle(int width)
    {
    	draw_rectangle(width, width); // ok
    }
    

This post (not mine) lists much more arguments against default arguments, some examples use features which haven't been covered yet so I'm only leaving the link.