Chapter Summary: Advanced Function Techniques and Templates

Congratulations on completing this chapter! You've learned powerful techniques that make C++ code more flexible and reusable. Let's review the key concepts with examples.

Defining Overloaded Functions

Function overloading lets you define multiple functions with the same name, differentiated by their parameter lists:

#include <iostream>
#include <string_view>

void print(int value) {
    std::cout << "Integer: " << value << '\n';
}

void print(double value) {
    std::cout << "Double: " << value << '\n';
}

void print(std::string_view value) {
    std::cout << "String: " << value << '\n';
}

int main() {
    print(42);        // Calls print(int)
    print(3.14);      // Calls print(double)
    print("Hello");   // Calls print(std::string_view)
    return 0;
}

The compiler selects the appropriate version based on the arguments you provide. Return types alone cannot differentiate overloaded functions.

Distinguishing Between Overloads

Function overloads are differentiated by their parameter lists. The number, types, and order of parameters all matter:

void process(int x, double y);    // Different from below
void process(double x, int y);    // Order matters

void calculate(int x);            // Different from below
void calculate(int x, int y);     // Number of parameters matters

Resolving Ambiguous Overloads

When the compiler resolves function calls, it follows a specific order:

  1. Exact matches (including trivial conversions)
  2. Matches via numeric promotions
  3. Matches via numeric conversions
  4. Matches via user-defined conversions

An ambiguous match occurs when multiple functions match equally well:

void foo(int x);
void foo(double x);

int main() {
    foo(5L);   // Error: ambiguous - long converts equally to int and double
    return 0;
}

Explicitly Deleted Functions

The = delete specifier prevents specific function calls from compiling:

void processValue(int x) {
    std::cout << "Processing: " << x << '\n';
}

void processValue(double) = delete;  // Prevent double calls

int main() {
    processValue(5);      // OK
    // processValue(3.14); // Error: use of deleted function
    return 0;
}

Deleted functions participate in overload resolution—if chosen as the best match, compilation fails with an error.

Optional Parameter Values

Default arguments provide fallback values for function parameters:

#include <iostream>
#include <string_view>

void greet(std::string_view name, std::string_view greeting = "Hello") {
    std::cout << greeting << ", " << name << "!\n";
}

int main() {
    greet("Alice");              // Uses default: "Hello, Alice!"
    greet("Bob", "Good morning"); // "Good morning, Bob!"
    return 0;
}

Parameters with defaults must appear rightmost in the parameter list. Default arguments aren't part of a function's signature and don't help differentiate overloaded functions.

Writing Generic Functions

Function templates are blueprints for generating functions. They use type template parameters (placeholders for types) that get replaced with actual types when instantiated:

#include <iostream>

template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    std::cout << max(3, 7) << '\n';       // Uses max<int>
    std::cout << max(3.5, 2.1) << '\n';   // Uses max<double>
    return 0;
}

Generating Functions from Templates

The process of creating actual functions from templates is called instantiation. When instantiation occurs due to a function call, it's implicit instantiation:

template <typename T>
T square(T value) {
    return value * value;
}

int main() {
    int a{ square(5) };        // Implicitly instantiates square<int>
    double b{ square(2.5) };   // Implicitly instantiates square<double>

    // Explicit template argument
    auto c = square<long>(10); // Explicitly instantiates square<long>
    return 0;
}

Multi-Parameter Templates

Templates can use multiple type parameters, allowing functions to accept arguments of different types:

#include <iostream>

template <typename T, typename U>
auto add(T a, U b) {
    return a + b;  // Return type deduced by compiler
}

int main() {
    auto result1 = add(5, 3.14);     // int + double = double
    auto result2 = add(2.5f, 10);    // float + int = float

    std::cout << result1 << '\n';    // 8.14
    std::cout << result2 << '\n';    // 12.5
    return 0;
}

The auto return type lets the compiler deduce the return type from the function body.

Compile-Time Value Parameters

Templates can also use value parameters (like integers) known at compile time:

#include <iostream>

template <typename T, int size>
T sumArray(const T (&arr)[size]) {
    T total{};
    for (int i = 0; i < size; ++i) {
        total += arr[i];
    }
    return total;
}

int main() {
    int numbers[] = {1, 2, 3, 4, 5};
    std::cout << sumArray(numbers) << '\n';  // 15

    double values[] = {1.5, 2.5, 3.0};
    std::cout << sumArray(values) << '\n';   // 7.0
    return 0;
}

Non-type template parameters are useful for array sizes, configuration constants, and other compile-time values.

Organizing Templates Across Files

Unlike regular functions, template definitions must be visible wherever they're used. Typically, entire template definitions go in header files:

// math_utils.h
#pragma once

template <typename T>
T clamp(T value, T min, T max) {
    if (value < min) return min;
    if (value > max) return max;
    return value;
}
// main.cpp
#include "math_utils.h"
#include <iostream>

int main() {
    std::cout << clamp(15, 0, 10) << '\n';  // 10
    std::cout << clamp(-5, 0, 10) << '\n';  // 0
    return 0;
}

For organization, implementations can go in separate .tpp or .inl files that are included by the header.

Key Terminology

Term Definition
Overloaded function Multiple functions sharing the same name, differentiated by parameters
Overload resolution Process of selecting which overloaded function to call
Ambiguous match When multiple functions match equally well
Default argument Pre-specified value for a parameter
Function template Pattern for generating related functions
Type template parameter Placeholder for a type in a template
Template parameter declaration Syntax defining template parameters
Function template instantiation Creating concrete functions from templates
Implicit instantiation Automatic instantiation triggered by function calls
Function instance Concrete function generated from a template
Template argument deduction Automatic determination of template types from arguments
Generic programming Programming style using templates
Abbreviated function template Function using auto parameters (C++20)
Non-type template parameter Template parameter representing a compile-time value

Looking Ahead

Templates are fundamental to modern C++ and power much of the standard library. In later chapters, you'll encounter:

  • Class templates for creating generic data structures
  • Template specialization for handling specific types differently
  • Variadic templates for functions accepting any number of arguments
  • Advanced template techniques like SFINAE and concepts

Understanding function templates provides the foundation for these more advanced topics. You'll find templates everywhere in professional C++ code—they're essential tools for writing flexible, reusable software.