1.3 Functions in C++
Function Basics — quick recap
returnType functionName(parameterList) {
// body
return value;
}
Example:
int add(int a, int b) {
return a + b;
}
int main() {
cout << add(5, 3); // 8
return 0;
}
C++ inherits all C function features and adds:
- Default arguments
- Function overloading
- Reference parameters
- Inline functions
- Templates (Unit IV)
- Lambda expressions (C++11+)
---
Default Arguments
A default argument allows a function to be called with fewer arguments than parameters — missing parameters use the default value.
void greet(string name = "Friend", string greeting = "Hello") {
cout << greeting << ", " << name << "!" << endl;
}
int main() {
greet(); // Hello, Friend!
greet("Rohit"); // Hello, Rohit!
greet("Rohit", "Namaste"); // Namaste, Rohit!
return 0;
}
Rules for default arguments
- Defaults must be specified from right to left — once you give one a default, all to its right must also have defaults.
void f(int a, int b = 5, int c = 10); // OK
void f(int a = 1, int b, int c = 10); // ERROR — b needs default too
- Defaults can only be specified once — either in the declaration or the definition, not both.
- Default values are evaluated at the call site — they can be expressions or even function calls.
- Default values are not part of the function signature for overload resolution.
Why use default arguments?
- Backward compatibility — add a parameter without breaking callers
- Convenience — common case needs no argument
- Cleaner API — fewer overloads needed
Equivalent without default arguments (function overloading)
void greet() { cout << "Hello, Friend!" << endl; }
void greet(string name) { cout << "Hello, " << name << "!" << endl; }
void greet(string name, string greeting) {
cout << greeting << ", " << name << "!" << endl;
}
Default arguments achieve the same with less code.
---
Function Overloading
Function overloading = defining multiple functions with the same name but different parameter lists.
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
add(2, 3); // calls int add(int, int)
add(2.5, 3.5); // calls double add(double, double)
add(1, 2, 3); // calls int add(int, int, int)
Rules for overloading
| Can differ in | Cannot differ in |
|---|---|
| Number of parameters | Return type alone |
| Type of parameters | Default values alone |
| Order of parameters |
Compiler's selection rule (name mangling)
The compiler renames overloaded functions internally based on parameter types. int add(int, int) may become _Z3addii; double add(double, double) may become _Z3adddd. So at the linker level there is no ambiguity.
Use cases for overloading
- Constructors with different argument lists
- Mathematical operations on different types
- Print functions for different objects
---
Parameter Passing
C++ supports three ways to pass arguments to a function:
1. Pass-by-Value
void modify(int x) {
x = 100; // changes only the local copy
}
int a = 5;
modify(a);
cout << a; // 5 — unchanged
A copy is made. Changes in the function do not affect the caller's variable. Cheap for primitives, expensive for large objects.
2. Pass-by-Reference
void modify(int& x) {
x = 100; // changes the original variable
}
int a = 5;
modify(a);
cout << a; // 100 — changed
The parameter is an alias for the argument. No copy. Caller's variable can be modified.
3. Pass-by-Pointer
void modify(int* p) {
*p = 100; // changes the variable pointed to
}
int a = 5;
modify(&a);
cout << a; // 100 — changed
The parameter is a pointer to the argument. Must use * to access value, and caller must pass with &.
Comparison — value vs reference vs pointer
| Aspect | Value | Reference | Pointer |
|---|---|---|---|
| Copy made? | Yes | No | No (copy of address) |
| Caller can be modified? | No | Yes | Yes (via dereference) |
| Syntax in declaration | int x | int& x | int* x |
| Syntax in call | f(a) | f(a) | f(&a) |
| Access in function | x | x | *x |
| Can be null? | N/A | No | Yes |
| Reseatable? | N/A | No | Yes |
| Best for | Small data, safety | Large objects, output params | Optional output, dynamic memory |
Const reference — the most useful idiom
void printVector(const vector<int>& v) { // no copy, no modification
for (int x : v) cout << x << " ";
}
This is the C++ idiom for passing large containers / objects — efficient AND safe.
---
Inline Functions
An inline function is a function whose body is inserted (expanded) at the call site, eliminating function-call overhead.
inline int square(int x) {
return x * x;
}
int y = square(5); // compiler may expand to: int y = 5 * 5;
Why inline?
- Avoids call overhead — no jump, no stack frame
- Enables optimisation — compiler sees the body in context
- Useful for tiny functions called millions of times
How it works
- Programmer hints to compiler: "consider inlining this"
- Compiler decides — may inline, may not (it's a hint, not a command)
- If inlined, function call → body substitution
- Function symbol may still exist for non-inlined uses
When to use inline
✅ Small functions (1-3 lines, no loops) ✅ Frequently called functions ✅ Performance-critical paths ✅ Getter / setter methods in classes
When NOT to use inline
❌ Large functions (code bloat) ❌ Functions with loops or recursion ❌ Functions whose address is taken (need a real function symbol) ❌ Virtual functions (usually can't be inlined)
Macros vs inline functions
| Aspect | Macro (#define) | Inline Function |
|---|---|---|
| Resolution | Preprocessor — text substitution | Compiler |
| Type safety | None | Yes |
| Debugger support | Hard | Normal |
| Side effects | Risky: #define SQR(x) ((x)*(x)) → SQR(i++) evaluates twice | Single evaluation |
| Scope | Global | Respects scope |
| Recommended | No (legacy) | Yes |
Modern C++ strongly prefers inline over macros for code-substitution.
---
Type Conversion (more)
Already covered in Lesson 2; here's how it interacts with overloading:
void print(int x) { cout << "int: " << x << endl; }
void print(double x) { cout << "double: " << x << endl; }
print(5); // int
print(5.5); // double
print(5.0f); // ambiguous? — actually picks double (float promotes)
print('A'); // int (char promotes to int)
The compiler picks the best match using implicit conversion rules.
---
Functions as First-Class Citizens
C++ supports function pointers (from C), functors (objects with operator()), and lambdas (C++11+):
Function pointer
int (*fp)(int, int) = &add;
fp(2, 3); // 5
Functor (function object)
class Adder {
public:
int operator()(int a, int b) { return a + b; }
};
Adder add;
add(2, 3); // 5 — looks like a function call
Lambda (C++11)
auto add = [](int a, int b) { return a + b; };
add(2, 3); // 5
Lambdas are widely used in modern C++ — STL algorithms, callbacks, threading.
---
Friend Functions (preview — covered in Unit II)
A friend function is a non-member function that has access to a class's private members:
class A {
private:
int data;
public:
A(int d) : data(d) {}
friend void print(A&); // grants access
};
void print(A& a) {
cout << a.data; // can access private!
}
We cover this in detail in Unit II.
---
Key Terms — Lesson 1.3
The terms below define C++'s function-related vocabulary — every PYQ on default arguments, overloading, inline, or parameter passing expects fluent use.
Function — A named block of code that takes inputs (parameters), performs an action, and optionally returns a result. The basic unit of code reuse in C and C++.
Function Prototype / Declaration — A statement that declares a function's signature (return type, name, parameter types) without defining the body. Allows the function to be called before its definition appears. Typically lives in header files.
Function Definition — The full implementation of a function — its prototype plus the body. There can be many declarations of a function but only one definition per program (the One Definition Rule).
Parameter vs Argument — A parameter is the variable in the function's declaration (int x in void f(int x)). An argument is the value passed at the call site (5 in f(5)). The two terms are often used loosely but mean different things.
Default Argument — A C++ feature: a function parameter can have a default value used when the caller omits the argument. void greet(string name = "Friend"); lets greet() or greet("Rohit") both compile. Defaults must be at the right end of the parameter list; you cannot have a default in the middle followed by a non-default.
Function Overloading — Defining multiple functions with the same name but different parameter lists (different number, types, or order of parameters). The compiler picks the right overload based on the call's arguments — compile-time polymorphism. Return type alone cannot distinguish overloads.
Name Mangling — The compiler's technique for implementing function overloading: each overload's internal symbol name encodes its parameter types (_Z3addii for int add(int, int) etc.). Name mangling is why C++ code cannot directly call C functions without extern "C".
Pass-by-Value — Function-call style where the argument is copied into the parameter. The function operates on the copy; the original is untouched. Safe but expensive for large objects.
Pass-by-Reference — Function-call style where the parameter is a reference to the argument (declared with &). The function operates on the original, can modify it, and avoids the copy cost. C++'s preferred idiom for output parameters and large objects.
Pass-by-Pointer — Function-call style where the parameter is a pointer to the argument. The function dereferences the pointer to access or modify the original. Predates references; still used for optional parameters (pass nullptr to skip) and dynamic arrays.
Inline Function — A function annotated with the inline keyword as a hint to the compiler to substitute the function body at the call site instead of generating a call/return. Avoids call overhead for tiny functions. The compiler may ignore the hint; in modern C++ the keyword's bigger role is allowing the function to be defined in headers (multiple translation units) without violating the One Definition Rule.
Macro vs Inline Function — Two ways to avoid function-call overhead. Macros (#define SQUARE(x) ((x)*(x))) are textual substitution by the preprocessor — no type checking, no scoping, side effects can multiply (SQUARE(i++) evaluates i++ twice). Inline functions are real functions — type-checked, scoped, evaluate arguments exactly once. Inline functions are always preferred over macros in modern C++.
Recursive Function — A function that calls itself, directly or indirectly, with a base case to terminate. Classical examples: factorial, Fibonacci, tree traversal. Most recursive functions should not be marked inline because the substitution cannot bottom out.
Friend Function — A non-member function declared with the friend keyword inside a class, granting it access to the class's private and protected members. Friend functions are not methods (no this pointer) but can read/write private data. Covered in detail in Unit II.
One Definition Rule (ODR) — A C++ rule: every entity (function, variable, class) must have exactly one definition across the entire program. Multiple declarations are fine; multiple definitions are a linker error. Inline functions and templates are exempt — they can be defined in multiple translation units provided every definition is identical.
Function Signature — The combination of a function's name and parameter list (types, count, order, and const/volatile qualifiers on member functions). Two functions with the same signature cannot coexist; two with different signatures can overload.
Overload Resolution — The compiler's algorithm for picking the right overload at a call site. The compiler ranks each candidate by how well its parameters match the arguments (exact match, promotion, conversion); the best match wins. Ambiguous calls (multiple equally-good matches) are compile errors.
Default Argument Evaluation — Default arguments are evaluated at each call, not at function definition. So void f(int x = currentTime()); calls currentTime() every time f() is invoked, not once at declaration.
---
Study deep
- Default arguments + overloading can conflict.
void f(int a, int b = 5);ANDvoid f(int a);— ambiguous when calledf(1). The compiler errors. Don't mix the two for the same parameter count.
- Pass-by-reference is the C++ idiom for output parameters. Java/C# don't have this — they use return values or wrapper objects. C++
outparameters via reference are idiomatic but be careful — they hide that the function modifies the caller's data.
inlineis more about linkage than performance. In modern C++, the keyword's main job is allowing the function to be defined in multiple translation units without violating the One Definition Rule. The compiler's inlining decision is mostly automatic.
- The compiler can inline non-
inlinefunctions. Modern compilers inline aggressively based on heuristics. Theinlinekeyword is a hint and matters less than it once did.
- Function overloading + templates = powerful generic code. Combined with templates, overloading enables zero-cost abstractions — code that's as generic as Python but as fast as C.
PYQ pattern (very common): "What are default arguments? Explain with example. State its rules." — Define; show example (greet(name="Friend", greeting="Hello")); state 4 rules (right-to-left, declared once, evaluated at call, not in signature).
PYQ pattern: "Explain function overloading with example." — Define; show 3 overloads of add; state rules (parameter count/type/order can differ; return type alone cannot); explain name mangling briefly.
PYQ pattern: "Differentiate pass-by-value, pass-by-reference, and pass-by-pointer." — Tabulate the 5-6 differences; one short code example each.
PYQ pattern: "Explain inline functions. Differentiate inline functions and macros." — Define; show example; list when to use (small, frequent); when not (large, recursion); table 5 differences vs macros (type safety, side effects, etc.).