C++ Cheatsheet

Templates

Use this C++ reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Function Templates

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

max(3, 5);          // T deduced as int
max(3.0, 5.0);      // T deduced as double
max<int>(3, 5);     // explicit template argument

// Multiple type parameters
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {   // trailing return type
    return a + b;
}

// C++14: auto return type deduction
template<typename T, typename U>
auto multiply(T a, U b) { return a * b; }

// Non-type template parameter
template<int N>
void printN() { std::cout << N << "\n"; }
printN<42>();

// Template with default argument
template<typename T = int>
T zero() { return T{}; }
zero();        // returns int 0
zero<double>(); // returns 0.0

Class Templates

template<typename T>
class Stack {
    std::vector<T> data_;
public:
    void push(const T& val)  { data_.push_back(val); }
    void push(T&& val)       { data_.push_back(std::move(val)); }
    T&   top()               { return data_.back(); }
    void pop()               { data_.pop_back(); }
    bool empty() const       { return data_.empty(); }
    std::size_t size() const { return data_.size(); }
};

Stack<int>         si;
Stack<std::string> ss;

// Class template with non-type parameter
template<typename T, std::size_t N>
class Array {
    T data_[N];
public:
    T&   operator[](std::size_t i)       { return data_[i]; }
    const T& operator[](std::size_t i) const { return data_[i]; }
    std::size_t size() const             { return N; }
    T* begin() { return data_; }
    T* end()   { return data_ + N; }
};

Array<int, 4> a;

Template Specialization

// Primary template
template<typename T>
struct IsPointer { static constexpr bool value = false; };

// Full specialization for T*
template<typename T>
struct IsPointer<T*> { static constexpr bool value = true; };

IsPointer<int>::value;    // false
IsPointer<int*>::value;   // true

// Partial specialization (class templates only)
template<typename T, typename U>
struct Pair { T first; U second; };

template<typename T>
struct Pair<T, T> { T first, second; };  // partial: both types same

// Function template full specialization
template<>
const char* max<const char*>(const char* a, const char* b) {
    return std::strcmp(a, b) > 0 ? a : b;  // compare contents, not pointers
}

Variadic Templates

// Base case
void print() {}

// Recursive variadic
template<typename T, typename... Rest>
void print(T first, Rest... rest) {
    std::cout << first;
    if constexpr (sizeof...(rest) > 0) std::cout << " ";
    print(rest...);
}
print(1, 2.5, "hello");   // "1 2.5 hello"

// sizeof... — count pack elements
template<typename... Ts>
constexpr std::size_t count() { return sizeof...(Ts); }

// Fold expressions (C++17)
template<typename... Ts> auto sum(Ts... vs)  { return (... + vs); }      // left fold
template<typename... Ts> auto prod(Ts... vs) { return (vs * ...); }      // right fold
template<typename... Ts> auto anyTrue(Ts... vs) { return (... || vs); }

// Forward all args
template<typename F, typename... Args>
auto call(F&& f, Args&&... args) {
    return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}

Template Type Traits (<type_traits>)

#include <type_traits>

// Type queries (value is constexpr bool)
std::is_integral_v<int>          // true
std::is_floating_point_v<double> // true
std::is_pointer_v<int*>          // true
std::is_reference_v<int&>        // true
std::is_const_v<const int>       // true
std::is_same_v<int, int>         // true
std::is_base_of_v<Base, Derived> // true
std::is_convertible_v<int, double> // true
std::is_trivially_copyable_v<T>
std::is_default_constructible_v<T>
std::is_copy_constructible_v<T>
std::is_move_constructible_v<T>
std::is_nothrow_move_constructible_v<T>

// Type transformations
std::remove_const_t<const int>   // int
std::remove_reference_t<int&>    // int
std::remove_pointer_t<int*>      // int
std::add_const_t<int>            // const int
std::add_lvalue_reference_t<int> // int&
std::decay_t<int[3]>             // int*  (array → pointer, etc.)
std::underlying_type_t<MyEnum>   // int (enum's underlying type)
std::common_type_t<int, double>  // double
std::conditional_t<B, T, F>      // T if B else F
std::enable_if_t<cond, T>        // T if cond, else SFINAE error

SFINAE and enable_if

// Enable function only when T is integral
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
square(T x) { return x * x; }

// Alternative: defaulted template parameter
template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
T cube(T x) { return x * x * x; }

Concepts (C++20)

Concepts replace SFINAE with readable constraints.

#include <concepts>

// Predefined concepts (<concepts>)
template<std::integral T>       T add(T a, T b) { return a + b; }
template<std::floating_point T> T div(T a, T b) { return a / b; }
template<std::copyable T>       T clone(T x)    { return x; }

// available: integral, floating_point, signed_integral,
//            unsigned_integral, same_as<T,U>, derived_from<D,B>,
//            convertible_to<F,T>, default_initializable, copy_constructible,
//            move_constructible, copyable, movable, semiregular, regular,
//            invocable<F,Args...>, predicate<F,Args...>, equality_comparable, ...

// Define your own concept
template<typename T>
concept Printable = requires(T t) {
    { std::cout << t } -> std::same_as<std::ostream&>;
};

template<Printable T>
void printIt(T val) { std::cout << val << "\n"; }

// requires clause on function
template<typename T>
requires std::integral<T> && (sizeof(T) >= 4)
T bigInt(T x) { return x; }

// requires expression inside concept
template<typename T>
concept Container = requires(T c) {
    c.begin();
    c.end();
    c.size();
    typename T::value_type;
};

Template Aliases

template<typename T>
using Vec = std::vector<T>;

Vec<int> v = {1, 2, 3};

template<typename K, typename V>
using Map = std::unordered_map<K, V>;

if constexpr in Templates (C++17)

template<typename T>
std::string describe(T val) {
    if constexpr (std::is_integral_v<T>)
        return "int: " + std::to_string(val);
    else if constexpr (std::is_floating_point_v<T>)
        return "float: " + std::to_string(val);
    else
        return "other";
}

The discarded branch is not instantiated (no compile error for invalid operations in it).

Template Template Parameters

template<template<typename> class Container, typename T>
void fill(Container<T>& c, T val) {
    std::fill(c.begin(), c.end(), val);
}

CRTP (Curiously Recurring Template Pattern)

// Static polymorphism — no vtable overhead
template<typename Derived>
class Base {
public:
    void interface() {
        static_cast<Derived*>(this)->implementation();
    }
};

class Concrete : public Base<Concrete> {
public:
    void implementation() { std::cout << "Concrete\n"; }
};

Concrete c;
c.interface();  // calls Concrete::implementation() at compile time

Explicit Instantiation

// Force instantiation in one TU (reduces compile times)
// In .cpp:
template class Stack<int>;           // explicit instantiation definition
template int max<int>(int, int);

// In header — declare but don't instantiate in including TUs:
extern template class Stack<int>;    // explicit instantiation declaration

Common Template Gotchas

  • typename vs class in template parameters: interchangeable; use typename to disambiguate dependent names (typename T::type).
  • Two-phase lookup — names used in templates are looked up at definition time (non-dependent) and instantiation time (dependent). Use this->member in derived class templates.
  • Template definitions must be visible at instantiation — put them in headers (not .cpp), or use explicit instantiation.
  • Avoid overuse of auto in templates — can lead to surprising deduced types; use concepts to constrain.