C++ Programming

Functions, References, Overloading and Inline Definitions

PGCP-AC

Functions divide a program into named operations with explicit inputs and results. A useful function has one responsibility, a clear ownership contract and a predictable failure policy. C++ supports value and reference parameters, overloads, default arguments, inline definitions, recursion and indirect calls.

1. Declarations and definitions

A declaration introduces a function before it is called:

double area(double radius);

A definition supplies its body:

double area(double radius) {
    return 3.141592653589793 * radius * radius;
}

The declaration lets the compiler check calls. A used ordinary function still needs one linked definition. Repeated declarations must agree in parameter and return types.

2. Calls and return values

A call initializes parameters from arguments and transfers control to the body:

double result = area(4.0);

A return statement exits the current function. A non-void function supplies a compatible result. A void function returns no value. Except for main, reaching the end of a value-returning function without returning produces undefined behaviour when its result is used.

Returning an object by value is safe and normally efficient. Copy elision can construct the result directly in its destination and move semantics can transfer resources when elision does not apply.

3. Pass by value

void increment(int value) {
    ++value;
}

The parameter is a separate object initialized from the argument, so changing value does not change the caller's int. Value passing is natural for small scalars, pointers, iterators and values the function needs to own.

For a class type, value passing copies or moves. A function that stores its argument can accept by value and move from the local parameter:

void set_name(std::string name) {
    member_name = std::move(name);
}

4. Lvalue references

A reference is an alias and must be initialized:

int number = 10;
int& alias = number;
alias = 20;

This changes number. A reference cannot later be reseated; assigning through it changes the referred object.

A non-const lvalue-reference parameter modifies a caller object:

void swap_values(int& left, int& right) {
    int temporary = left;
    left = right;
    right = temporary;
}

Such a parameter normally binds only to a modifiable lvalue. The signature makes the possible output effect visible.

5. Const references

A const lvalue reference gives read access without an ordinary copy:

std::size_t count_words(const std::string& text);

It is appropriate for large input objects that the function does not own. It can bind to const objects and many temporary values. Binding a temporary to a local const reference can extend its lifetime to the reference's scope, but storing references beyond that scope can still dangle.

Small types such as int and double are usually simpler by value.

6. Rvalue references

An rvalue reference, written T&&, binds to temporary or expiring values and enables move-aware interfaces:

void consume(std::string&& text);
consume(std::string{"data"});

A named rvalue-reference variable is an lvalue expression. Use std::move when an actual transfer from that named object is intended. Do not add rvalue-reference overloads without a clear ownership reason; pass-by-value may be simpler.

7. Pointer parameters

A pointer can express optional access:

void reset(int* value) {
    if (value != nullptr) {
        *value = 0;
    }
}

Use a reference when an object is required and a pointer when null has a defined meaning. When a pointer denotes a sequence, pass its size or endpoint too; a pointer does not contain array length.

8. Returning references safely

A function may return a reference to an object that outlives the call:

int& element(std::vector<int>& values, std::size_t index) {
    return values.at(index);
}

The reference depends on the vector's lifetime and can be invalidated by reallocation.

Never return a reference or pointer to an automatic local:

int& invalid() {
    int value = 10;
    return value;
}

The local dies when the function returns, leaving dangling access. References can also dangle after owner destruction, container invalidation or temporary lifetime end.

9. Function overloading

Functions may share a name when parameter lists differ:

void print(int value);
void print(double value);
void print(const std::string& value);

The compiler collects candidates, removes those that cannot accept the arguments, ranks conversions and selects one best viable function. Exact matches normally rank before promotions, which rank before broader standard conversions.

Return type alone cannot distinguish overloads. The desired assignment target does not choose between otherwise equal parameter lists.

10. Ambiguity and conversion ranking

void process(long);
void process(double);
// process(10); // potentially ambiguous

If no unique best viable function exists, compilation fails. Correct the interface or supply an explicitly typed argument. Do not depend on a guess.

Too many overloads involving numeric conversions, user-defined conversions, forwarding references and defaults make an API difficult to predict. Different semantic operations often deserve different names.

11. Const member overloads

class Buffer {
public:
    char& at(std::size_t index);
    const char& at(std::size_t index) const;
};

A modifiable Buffer selects the non-const member and can receive modifiable access. A const Buffer selects the const member.

Top-level const on a by-value parameter is not part of a function signature, so declarations taking int and const int by value do not form distinct overloads.

12. Default arguments

void connect(std::string host,
             int port = 443,
             bool secure = true);

Defaults supply omitted trailing arguments. They are substituted at the call site and normally belong in the public declaration, not repeated in the definition. After one parameter has a default, following parameters require defaults in that declaration.

Defaults can conflict with overloads and cause ambiguity. When several Boolean or related settings accumulate, an options structure communicates meaning better.

13. Inline definitions

An inline function may have equivalent definitions in several translation units, allowing a header definition:

inline int square(int value) {
    return value * value;
}

Functions defined inside a class definition are implicitly inline. Template definitions are commonly placed in headers because instantiation requires visibility.

The keyword does not force call-site machine-code expansion. The optimizer may expand a function without it or retain a call to one declared inline. Its essential language role concerns multiple permitted definitions under the one-definition rule.

14. Constexpr functions

constexpr int power(int base, unsigned exponent) {
    int result = 1;
    for (unsigned i = 0; i < exponent; ++i) {
        result *= base;
    }
    return result;
}

constexpr int cells = power(2, 8);

A constexpr function can be evaluated during translation when its inputs and operations permit and can also run at runtime. It is implicitly inline. Constant evaluation is a semantic guarantee, not merely an optimization request.

15. Recursion

unsigned long long factorial(unsigned n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

Terminating recursion requires a reachable base case and progress toward it. Each call consumes an activation record, so excessive depth can exhaust the stack. Recursion naturally models trees, divide-and-conquer and backtracking; a loop may be clearer for linear repetition.

Termination does not guarantee numeric correctness: factorial overflows fixed-width integers quickly.

16. Function pointers

int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }

using Operation = int (*)(int, int);
Operation operation = &add;
int result = operation(3, 4);

A function pointer enables indirect invocation and can be null. Its type includes parameter and return types. It cannot store a capturing lambda or arbitrary stateful callable. std::function supports a wider family of callables with type-erasure cost, while templates provide compile-time callable polymorphism.

17. Lambdas

int factor = 3;
auto scale = [factor](int value) {
    return value * factor;
};

A lambda creates an unnamed function object. Value capture stores a value in that object. Reference capture depends on the original object's lifetime. A noncapturing lambda can convert to a compatible function pointer.

Lambdas keep small callback behaviour close to the algorithm that uses it.

18. Mathematical library functions

The cmath header declares overloaded operations such as std::sqrt, std::pow, std::sin, std::floor and std::round:

#include <cmath>

double diagonal(double width, double height) {
    return std::sqrt(width * width + height * height);
}

Check domain, range and floating-point assumptions. A negative square-root input commonly produces NaN rather than a C++ exception. For integer powers, an integer algorithm can preserve integer semantics and handle overflow deliberately.

19. Function design

A strong function has one coherent responsibility, a descriptive name, few parameters, explicit ownership and a clear failure policy. Use types instead of unexplained Boolean switches. Prefer a returned result object to several output parameters when it improves clarity.

The nodiscard attribute warns when ignoring a result is likely a defect. noexcept promises that no exception will escape; if one does, the program terminates, so use it only when the guarantee is real.

20. Practical considerations

  1. A prototype supplies type information before a call.
  2. Value parameters are separate objects initialized from arguments.
  3. An lvalue reference aliases the caller's object.
  4. A const reference avoids an ordinary copy and prohibits modification through it.
  5. Returning a local reference or pointer creates dangling access.
  6. Return type alone cannot distinguish overloads.
  7. Ambiguous overload resolution is a compile-time error.
  8. Default arguments fill omitted trailing arguments.
  9. Inline does not force call expansion.
  10. Recursion needs a base case and progress.
  11. Function pointers represent indirect calls with one signature.
  12. Mathematical functions are declared in cmath.

Continue learning

Related notes

Put this topic into timed practice

Open mock tests when you want full-exam pacing, or keep drilling in practice mode.