Templates and Generics

Templates and Generics

Function templates, class templates, constraints, and generic programming patterns.

Templates and Generics

Function templates

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

Class templates

template <typename T>
class Box {
public:
    explicit Box(T value) : value_{std::move(value)} {}
    const T& get() const { return value_; }

private:
    T value_;
};

Non-type template parameters

template <typename T, std::size_t N>
class FixedBuffer {
    std::array<T, N> data_{};
};

Concepts and requires

template <typename T>
concept Addable = requires(T a, T b) {
    a + b;
};

template <Addable T>
T sum(T a, T b) {
    return a + b;
}

Named constraints make the interface easier to read and keep failures closer to the call site.

Useful standard traits

template <typename T>
void describe(T&& value) {
    using Raw = std::remove_cvref_t<T>;
    if constexpr (std::is_integral_v<Raw>) {
        std::cout << "integral\n";
    }
}

Best-practice reminders

Abbreviated templates and named constraints

std::integral auto clamp_positive(std::integral auto value) {
    return value < 0 ? 0 : value;
}

Abbreviated templates are convenient for small interfaces, but named template parameters are usually clearer for bigger APIs.

requires clauses and nested requirements

template <typename T>
concept Reservable = requires(T container, std::size_t n) {
    container.reserve(n);
    typename T::value_type;
};

template <typename T>
requires Reservable<T>
void prepare(T& container, std::size_t n) {
    container.reserve(n);
}

Debugging template errors

template <std::integral T>
T twice(T value) {
    return value + value;
}

auto text = twice(std::string{"Ada"});

The fix is not to stare at the whole compiler trace. The fix is to notice that std::string does not model std::integral.

Useful modern traits and helpers

Pattern selection guide

When if constexpr helps

Use if constexpr to keep one generic algorithm readable instead of splitting simple type-based branches into separate overloads.

Example in practice

template <typename T>
T add(T a, T b) {
    return a + b;
}

Try this variation

Add a concept or `static_assert` so the template fails early for the wrong type. That is usually the first quality jump in generic code.

#include <concepts>

template <std::integral T>
T twice(T value) {
    return value + value;
}