Summary

Templates are C++‘s mechanism for writing code that works with any type, parameterised at compile time. A template<typename T> class or function generates separate, fully optimised machine code for each type it is instantiated with — no runtime overhead, but the types must be known at compile time. C++20 adds concepts to constrain template arguments with meaningful compile-time error messages. Lambdas and function objects are the primary practical tools built on the template mechanism.

(Stroustrup, A Tour of C++, 3rd ed., Chs. 7–8, see source-a-tour-of-cpp)


Class templates

A class template parameterises a class with one or more type arguments:

template<typename T>
class Vector {
public:
    explicit Vector(int s);
    ~Vector() { delete[] elem; }
 
    T& operator[](int i);
    const T& operator[](int i) const;
    int size() const { return sz; }
 
private:
    T* elem;
    int sz;
};

Instantiate with a concrete type:

Vector<char> vc(200);          // vector of chars
Vector<string> vs(17);         // vector of strings
Vector<list<int>> vli(45);     // vector of lists of ints

Each instantiation is a separate concrete class. Vector<double> and Vector<int> are distinct types with no runtime relationship.

template<class T> vs template<typename T>: these are synonyms. typename is more common in modern code.


Function templates

A function template is parameterised by type and the type is deduced from the arguments:

template<typename Sequence, typename Value>
Value sum(const Sequence& s, Value v)
{
    for (auto x : s)
        v += x;
    return v;
}
 
// Usage — types deduced automatically:
int total = sum(vec_of_ints, 0);
double avg = sum(vec_of_ints, 0.0);

No need to specify template arguments explicitly when they can be deduced. The compiler generates a concrete instantiation for each combination of types used.


Concepts (C++20)

Without constraints, template errors appear at instantiation time — deep in the compiler’s output, often unreadable. A concept is a compile-time predicate that constrains which types a template accepts:

template<Element T>           // Element is a concept
class Vector { /* ... */ };

If a type fails the concept check, the compiler reports an error at the point of use with a human-readable message.

Standard library concepts live in <concepts>. Examples:

  • std::copyable<T> — T can be copied
  • std::integral<T> — T is an integral type
  • std::floating_point<T> — T is a floating-point type
  • std::invocable<F, Args...> — F is callable with Args
template<std::integral T>
T twice(T x) { return x + x; }
 
twice(3);       // OK
twice(3.14);    // ERROR: 3.14 is not integral — clear message

C++20 compatibility: Unreal Engine 5 supports C++17 and partial C++20. Concepts require C++20 and may not be available in all project configurations.


Value template arguments

Templates can also take non-type (value) arguments:

template<typename T, int N>
struct Buffer {
    T elem[N];
    constexpr int size() { return N; }
};
 
Buffer<char, 1024> buf;   // statically-sized buffer, no heap allocation

Value arguments must be constant expressions — known at compile time. This is used for fixed-size containers, compile-time configuration, and policy objects.


Template argument deduction (CTAD)

In C++17+, the compiler can deduce class template arguments from constructor arguments:

pair<int, double> p1 = {1, 5.2};   // explicit
pair p2 = {1, 5.2};                 // CTAD: deduces pair<int, double>
 
Vector v {1, 2, 3};    // deduces Vector<int>

CTAD removes redundancy in many common cases. Be aware that deduction is not always obvious — Vector vs {"Hello", "World"} deduces Vector<const char*>, not Vector<string>.


Function objects (functors)

A function object (functor) is an object of a class that defines operator(). It can carry state (unlike a plain function) and is callable like a function:

template<typename T>
class Less_than {
    const T val;
public:
    Less_than(const T& v) : val{v} {}
    bool operator()(const T& x) const { return x < val; }
};
 
Less_than<int> lt42 {42};
bool b = lt42(37);   // true: 37 < 42

Function objects are the mechanism behind STL algorithms:

sort(v.begin(), v.end(), Less_than<int>{10});   // sort by: x < 10

They are more efficient than function pointers because operator() can be inlined.


Lambda expressions

Lambda expressions provide a concise syntax for creating anonymous function objects:

auto is_small = [](int x) { return x < 10; };
bool b = is_small(7);    // true

Capture lists specify how the lambda accesses variables from the enclosing scope:

CaptureMeaning
[]Capture nothing
[&]Capture all by reference
[=]Capture all by value (copy)
[x]Capture only x by value
[&x]Capture only x by reference
[this]Capture this (current object, by reference)
[*this]Capture copy of current object
int threshold = 42;
 
// Capture by reference — lambda can see changes to threshold
auto below_ref = [&](int x) { return x < threshold; };
 
// Capture by value — lambda has its own copy of threshold at time of creation
auto below_val = [=](int x) { return x < threshold; };

Generic lambda (auto parameter): accepted as any type, effectively a template lambda:

auto add = [](auto a, auto b) { return a + b; };
add(1, 2);         // int
add(1.5, 2.5);    // double

Lambdas in practice — the most common use is as arguments to standard algorithms:

vector<int> v {5, 1, 3, 2, 4};
sort(v.begin(), v.end(), [](int a, int b) { return a < b; });
 
auto count_big = count_if(v.begin(), v.end(), [](int x) { return x > 3; });

Templates vs C# generics

C++ TemplatesC# Generics
When resolvedCompile timeCompile time for value types; runtime for reference types
Code generatedSeparate code for each instantiationShared code for reference types (type erasure)
Runtime overheadZeroNegligible for value types; minimal boxing for reference types
Error messages (unconstrained)Poor (deep in instantiation)Moderate
Error messages (constrained)Good (concepts, C++20)Good (interfaces/where constraints)
Template metaprogrammingExtensive — can compute at compile timeLimited (primarily runtime)
Default function visibilityNot virtualDepends on base class

The practical implication: C++ templates generate a binary footprint proportional to the number of distinct type arguments. Ten instantiations of a large template class produce ten copies of the generated code. C# generics for reference types share one implementation.


In practice (Unreal Engine context)

Unreal Engine uses templates extensively:

  • TArray<T>, TMap<K,V>, TSet<T> — Unreal’s container types (analogues to vector, map, set)
  • TSharedPtr<T>, TWeakPtr<T> — Unreal’s smart pointer equivalents
  • TSubclassOf<T> — type-safe class reference for Blueprint
  • TOptional<T> — optional value (similar to std::optional)
  • Delegate templates: DECLARE_DYNAMIC_DELEGATE, DECLARE_DELEGATE_RetVal macros generate template-based callback types

Unreal’s templates follow the same principles as std templates, but are adapted for Unreal’s reflection system, UPROPERTY compatibility, and historical platform constraints.


Open questions

  • C++ template errors (without concepts) are notoriously hard to read. What diagnostic strategies help in practice — static assertions, type traits, or relying on IDE error highlighting?
  • Unreal Engine has its own reflection system that limits some template uses (e.g., UPROPERTY cannot be a template member). Where do these restrictions most commonly catch developers?