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
typenamevsclassin template parameters: interchangeable; usetypenameto 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->memberin derived class templates. - Template definitions must be visible at instantiation — put them in headers (not .cpp), or use explicit instantiation.
- Avoid overuse of
autoin templates — can lead to surprising deduced types; use concepts to constrain.