Passing std::vector to functions

Like any object in C++, a std::vector can be passed to functions. However, because vectors can contain substantial amounts of data, passing them by value creates expensive copies. The solution: pass vectors by reference.

Passing vectors by const reference

When a function needs to read vector data without modifying it, pass by const reference:

#include <iostream>
#include <vector>

void displayFirst(const std::vector<int>& numbers)
{
    if (!numbers.empty())
        std::cout << "First element: " << numbers[0] << '\n';
}

int main()
{
    std::vector temps { 72, 68, 75, 71, 69 };
    displayFirst(temps);

    return 0;
}

This avoids copying the entire vector while preventing accidental modifications.

Specifying element types explicitly

Because std::vector is a template, we must specify the element type when declaring function parameters:

#include <iostream>
#include <vector>

void showFirstItem(const std::vector<double>& values)  // Must specify <double>
{
    if (!values.empty())
        std::cout << values[0] << '\n';
}

int main()
{
    std::vector prices { 29.99, 15.50, 42.00 };
    showFirstItem(prices);

    return 0;
}

This also means the function only accepts vectors of that specific type:

#include <iostream>
#include <vector>

void showFirstItem(const std::vector<double>& values)
{
    if (!values.empty())
        std::cout << values[0] << '\n';
}

int main()
{
    std::vector prices { 29.99, 15.50, 42.00 };
    showFirstItem(prices);  // OK: vector<double>

    std::vector counts { 10, 20, 30 };
    showFirstItem(counts);  // Error: vector<int> doesn't match vector<double>

    return 0;
}

CTAD doesn't work for function parameters

You might think to use Class Template Argument Deduction (CTAD) to avoid specifying the type:

#include <iostream>
#include <vector>

void showFirstItem(const std::vector& values)  // Error: CTAD not allowed here
{
    if (!values.empty())
        std::cout << values[0] << '\n';
}

int main()
{
    std::vector prices { 29.99, 15.50, 42.00 };
    showFirstItem(prices);

    return 0;
}

Unfortunately, CTAD works for variable definitions but not for function parameters. We need another approach.

Using function templates for generic vector handling

Function templates let us write functions that work with vectors of any element type:

#include <iostream>
#include <vector>

template <typename T>
void showFirstItem(const std::vector<T>& values)
{
    if (!values.empty())
        std::cout << values[0] << '\n';
}

int main()
{
    std::vector prices { 29.99, 15.50, 42.00 };
    showFirstItem(prices);  // Instantiates showFirstItem(const std::vector<double>&)

    std::vector counts { 10, 20, 30 };
    showFirstItem(counts);  // Instantiates showFirstItem(const std::vector<int>&)

    return 0;
}

The compiler automatically instantiates the appropriate function version for each vector type.

Generic templates for maximum flexibility

For even greater flexibility, we can accept any container type:

#include <iostream>
#include <vector>

template <typename T>
void showFirstItem(const T& container)
{
    if (!container.empty())
        std::cout << container[0] << '\n';
}

int main()
{
    std::vector prices { 29.99, 15.50, 42.00 };
    showFirstItem(prices);

    std::vector counts { 10, 20, 30 };
    showFirstItem(counts);

    return 0;
}

This works with any type that supports empty() and operator[], making it extremely versatile. In C++20, you can use an abbreviated function template:

#include <iostream>
#include <vector>

void showFirstItem(const auto& container)
{
    if (!container.empty())
        std::cout << container[0] << '\n';
}

int main()
{
    std::vector prices { 29.99, 15.50, 42.00 };
    showFirstItem(prices);

    std::vector counts { 10, 20, 30 };
    showFirstItem(counts);

    return 0;
}

The trade-off: this ultra-generic approach might allow nonsensical types to compile until runtime errors occur.

Runtime assertions for vector length requirements

Consider a function that assumes a minimum vector length:

#include <iostream>
#include <vector>

template <typename T>
void displayThirdElement(const std::vector<T>& data)
{
    std::cout << data[2] << '\n';
}

int main()
{
    std::vector temps { 72, 68 };  // Only 2 elements!
    displayThirdElement(temps);     // Undefined behavior

    return 0;
}

This compiles but causes undefined behavior when executed. Add a runtime assertion to catch such errors:

#include <cassert>
#include <iostream>
#include <vector>

template <typename T>
void displayThirdElement(const std::vector<T>& data)
{
    assert(data.size() >= 3 && "Vector must have at least 3 elements");
    std::cout << data[2] << '\n';
}

int main()
{
    std::vector temps { 72, 68 };
    displayThirdElement(temps);  // Triggers assertion in debug builds

    return 0;
}

For compile-time length checking, prefer std::array instead of std::vector (covered in lesson 17.3).