I know, it’s been a while since the last time I published something newbies-friendly on my blog. The main reason is that most of my readers are either experienced devs or from C background having modest C++ encounter. But while programming in C++ you need a completely different mindset as both C & C++ belongs to different programming paradigm. And I always strive to show them a better way of doing things in C++. Anyway, I found the topic which is lengthy, reasonably complex(at least it was for me), newbies-friendly as well as energizing for experienced folks(if Modern C++ jargons, rules & features added) i.e. C++ Template.
I will start with a simple class/function template and as we move along, will increase the complexity. And also cover the advance topics like the variadic template, nested template, CRTP, template vs fold-expression, etc. But, yes! we would not take deeper dive otherwise this would become a book rather than an article.
Note: I would recommend you to use cppinsights online tool wherever you feel confused. It helps you to see Template Instances, Template Argument Deduction, etc. Basically, it helps you to see code from the compiler's perspective.

Terminology/Jargon/Idiom You May Face

C++ Template Types

Class Template

template <typename T1, typename T2>
class pair {
public:
    T1  first;
    T2  second;
};

pair<int, char> p1;
pair<float, float> p2;

Function Template

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

min<int>(4, 5);              // Case 1 
min<float>(4.1f, 5.1f);      // Case 2
In both of the above case, the template arguments used to replace the types of the parameters i.e. T.One additional property of template functions (unlike class template till C++17) is that the compiler can infer the template parameters based on the parameters passed to the function. So, passing <int> & <float> after the function name is redundant.

Union Template

template <typename T>
union test {
    uint8_t     ch[sizeof(T)];
    T           variable;
};
As you can see above, templatized unions are also particularly useful to represent a type simultaneously as a byte array.

Variable Template

template <class T>
constexpr T pi = T(3.1415926535897932385L); // variable template

cout << pi<float> << endl; // 3.14159
cout << pi<int> << endl;   // 3
template <uint32_t val>
constexpr auto fib = fib<val - 1> + fib<val - 2>;

template <>
constexpr auto fib<0> = 0;

template <>
constexpr auto fib<1> = 1;

cout << fib<10> << endl;    // 55

C++ Template Argument

Overriding Template Argument Deduction

template <typename T>
T min(T a, T b) {
    cout << typeid(T).name() << endl; // T will be deduce as `int`
    return a < b ? a : b;
}

min<int>(5.5f, 6.6f);     // Implicit conversion happens here

Default Template Arguments

template <class T, size_t N = 10>
struct array {
    T arr[N];
};

array<int> arr;

Template Argument Deduction

Function Template Argument Deduction

Function template argument deduction is done by comparing the types of function arguments to function parameters, according to rules in the Standard. Which makes function templates far more usable than they would otherwise be.
For example, given a function template like:
template <typename RanIt> 
void sort(RanIt first, RanIt last){
    // . . .
}

Class Template Argument Deduction(CTAD)

//...
pair p4{1, 'A'};               // Not OK until C++17: Can't deduce type in initialization 
//...

Inferring Template Argument Through Function Template

template <typename T1, typename T2>
pair<T1, T2> make_pair(T1&& t1, T2&& t2) {
    return {forward<T1>(t1), forward<T2>(t2)};
}
pair<int, char> p1{1, 'A'};          // Rather using this

auto p2 = make_pair(1, 2);           // Use this instead
auto p3 = make_pair<float>(1, 2.4f); // Or specify types explicitly

Template Argument Forwarding

C++ Template Reference Collapsing Rules

  1. T& & becomes T&
  2. T& && become T&
  3. T&& & becomes T&
  4. T&& && becomes T&&
template <typename T>
void f(T &&t);
int x = 0;

f(0); // deduces as rvalue reference i.e. f(int&&)
f(x); // deduces as lvalue reference i.e. f(int&)

Perfect Forwarding | Forwarding Reference | Universal Reference

template <typename T>
void func1(T &&t) {
    func2(std::forward<T>(t));  // Forward appropriate lvalue or rvalue reference to another function
}
template <typename... Args>
void func1(Args&&... args) {
    func2(std::forward<Args>(args)...);
}
Why Do We Need Forwarding Reference in First Place?

C++ Template Category

Full Template Specialization

Function Template Specialization

template <typename T>
T sqrt(T t) { /* Some generic implementation */ }

template<>
int sqrt<int>(int i) { /* Highly optimized integer implementation */ }

Class Template Specialization

template <typename T>       // Common case
struct Vector {
    void print() {}
};

template <>                 // Special case
struct Vector<bool> {
    void print_bool() {}
};

Vector<int> v1;
v1.print_bool();    // Not OK: Chose common case Vector<T>
v1.print()          // OK

Vector<bool> v2;    // OK : Chose special case Vector<bool>

Partial Template Specialization

Partial Class Template Specialization

template <typename T1, typename T2>     // Common case
struct Pair {
    T1 first;
    T2 second;

    void print_first() {}
};

template <typename T>    // Partial specialization on first argument as int
struct Pair<int, T> {
    void print() {}
};

// Use case 1 ----------------------------------------------------------
Pair<char, float> p1;    // Chose common case
p1.print_first();        // OK
// p1.print();           // Not OK: p1 is common case & it doesn't have print() method

// Use case 2 ----------------------------------------------------------
Pair<int, float> p2;     // Chose special case
p2.print();              // OK
// p2.print_first();     // Not OK: p2 is special case & it does not have print_first()

// Use case 3 ----------------------------------------------------------
// Pair<int> p3;         // Not OK: Number of argument should be same as Primary template

Partial Function Template Specialization

template <typename T, typename U>
void foo(T t, U u) {
    cout << "Common case" << endl;
}

// OK.
template <>
void foo<int, int>(int a1, int a2) {
    cout << "Fully specialized case" << endl;
}

// Compilation error: partial function specialization is not allowed.
template <typename U>
void foo<string, U>(string t, U u) {
    cout << "Partial specialized case" << endl;
}

foo(1, 2.1); // Common case
foo(1, 2);   // Fully specialized case

Alternative To Partial Function Template Specialization

template <typename T, typename std::enable_if_t<!std::is_pointer<T>::value> * = nullptr>
void func(T val) {  
    cout << "Value" << endl; 
}

template <typename T, typename std::enable_if_t<std::is_pointer<T>::value> * = nullptr>
void func(T val) {  // NOTE: function signature is NOT-MODIFIED
    cout << "Pointer" << endl; 
}

int a = 0;
func(a);
func(&a);

Non-Type Template Parameter

template <  class T, 
            size_t size>     // Non Type Template
T* begin(T (&arr)[size]) {   // Array size deduced implicitly
    return arr;
}

int arr[] = {1,2,3,4};
begin(arr);                  // Do not have to pass size explicitly 

Nested Template: Template Template Parameter

template<   
            template <typename> class C, 
            typename T
        >
void print_container(C<T> &c) {
    // . . .
}

template <typename T>
class My_Type {
    // . . .
};

My_Type<int> t;
print_container(t);

Variadic Template

Variadic Class Template

Implementing Unsophisticated Tuple Class(>=C++14)

template <typename... T>
struct Tuple { };
template<
            typename T, 
            typename... Rest
        >
struct Tuple<T, Rest...> {
    T               first;
    Tuple<Rest...>  rest;

    Tuple(const T& f, const Rest& ... r)
        : first(f)
        , rest(r...) {
    }
};

Tuple<bool> t1(false);                      // Case 1
Tuple<int, char, string> t2(1, 'a', "ABC"); // Case 2

How Does Variadic Class Template Works?

To understand variadic class template, consider use case 2 above i.e. Tuple<int, char, string> t2(1, 'a', "ABC");

You can visualize this as follows:

Tuple<int, char, string>
-> int first
-> Tuple<char, string> rest
    -> char first
    -> Tuple<string> rest
        -> string first
        -> Tuple<> rest
            -> (empty)
I have written a separate article on Variadic Template C++: Implementing Unsophisticated Tuple, if you are interested more in the variadic temple.

Variadic Function Template

void print() {}
template<   
            typename First, 
            typename... Rest                    // Template parameter pack
        >     
void print(First first, Rest... rest) {         // Function parameter pack
    cout << first << endl;
    print(rest...);                             // Parameter pack expansion
} 
print(500, 'a', "ABC");
template<   
            typename First, 
            typename... Rest
        >     
void print(First&& first, Rest&&... rest) {         
    if constexpr(sizeof...(rest) > 0) {             // Size of parameter pack
        cout << first << endl;
        print(std::forward<Rest>(rest)...);         // Forwarding reference
    }
    else {
        cout << first << endl;
    }
} 
How Does Variadic Function Template Works?
  1. void print(int first, char __rest1, const char* __rest2)
  2. void print(char first, const char* __rest1)
  3. void print(const char* first)

Fold Expressions vs Variadic Template

template <typename... Args>
void print(Args &&... args) {
    (void(cout << std::forward<Args>(args) << endl), ...);
}

Misc

C++ Template typename vs class

To Refer Dependent Types

template<typename container>
class Example {
    using t1 = typename container::value_type; // value_type depends on template argument of container
    using t2 = std::vector<int>::value_type;   // value_type is concrete type, so doesn't require typename
};

To Specify Template Template Type

template<
            template <typename, typename> class C, // `class` is must prior to C++17
            typename T, 
            typename Allocator
        >
void print_container(C<T, Allocator> container) {
    for (const T& v : container)
        cout << v << endl;
}

vector<int> v;
print_container(v);

C++11: Template Type Alias

template<typename T> 
using pointer = T*;

pointer<int> p = new int;   // Equivalent to: int* p = new int;


template <typename T>
using v = vector<T>;

v<int> dynamic_arr;         // Equivalent to: vector<int> dynamic_arr;

C++14/17: Template & auto Keyword

void print(auto &c) { /*. . .*/ }

// Equivalent to

template <typename T>
void print(T &c) { /*. . .*/ }

C++20: Template Lambda Expression

template <typename T>
void f(std::vector<T>&    vec) {
    //. . .
}
auto f = []<typename T>(std::vector<T>&  vec) {
    // . . .
};

std::vector<int> v;
f(v);

Explicit Template Instantiation

value.hpp

#pragma once

template <typename T>
class value {
    T val;
public:
    T get_value();
};

value.cpp

#include "value.hpp"

template <typename T>
T value<T>::get_value() { 
    return val; 
}

main.cpp

#include "value.hpp"

int main() {
    value<int> v1{9};
    cout << v1.get_value() << endl;
    return 0;
}
/tmp/main-4b4bef.o: In function `main':
main.cpp:(.text+0x1e): undefined reference to `value<int>::get_value()'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
compiler exit status 1

C++ Template Example Use Cases

Curiously Recurring Template Pattern

CRTP widely employed for static polymorphism or code reusability without bearing the cost of virtual dispatch mechanism. Consider the following code:
template <typename specific_animal>
struct animal {
    void who() { implementation().who(); }

private:
    specific_animal &implementation() { return *static_cast<specific_animal *>(this); }
};

struct dog : animal<dog> {
    void who() { cout << "dog" << endl; }
};

struct cat : animal<cat> {
    void who() { cout << "cat" << endl; }
};


template <typename specific_animal>
void who_am_i(animal<specific_animal> *animal) {
    animal->who();
}


who_am_i(new dog); // Prints `dog`
who_am_i(new cat); // Prints `cat`

Passing `std` Container as C++ Template Argument

template <typename C>
void print_container(const C &container) {
    for (const auto &v : container)
        cout << v << endl;
}

Passing std::vector to C++ Template Function

Naive Way to Capture Container’s Value Type

template<
            typename C, 
            typename T = typename C::value_type
        >
void print_container(const C &container) {
    for (const T &v : container)
        cout << v << endl;
}

Capturing Container’s Value Type Explicitly

template<
            class T,
            class Allocator = std::allocator<T>
        > 
class vector;
template<
            template <typename, typename> class C, 
            typename T, 
            typename Allocator
        >
void print_container(C<T, Allocator> container) {
    for (const T& v : container)
        cout << v << endl;
}

Passing Any Container to C++ Template Function

template<
            template <typename...> class C, 
            typename... Args
        >
void print_container(C<Args...> container) {
    for (const auto &v : container)
        cout << v << endl;
}

vector<int>     v{1, 2, 3, 4}; // takes total 2 template type argument
print_container(v); 

set<int>        s{1, 2, 3, 4}; // takes total 3 template type argument
print_container(s);

Passing Container-of-Container/2D-std::vector as C++ Template Argument

Explicit & Complex Solution

template<
            template <typename, typename> class C1,
            template <typename, typename> class C2,
            typename Alloc_C1, typename Alloc_C2,
            typename T
        >
void print_container(const C1<C2<T, Alloc_C2>, Alloc_C1> &container) {
    for (const C2<T, Alloc_C2> &container_in : container)
        for (const T &v : container_in)
            cout << v << endl;
}

Neat Solution

template<   
            typename T1,
            typename T2 = typename T1::value_type,
            typename T3 = typename T2::value_type
        >
void print_container(const T1 &container) {
    for (const T2 &e : container)
        for (const T3 &x : e)
            cout << x << endl;
}

Generic Solution: Using Variadic Template

template<
            template <typename...> class C, 
            typename... Args
        >
void print_container(C<Args...> container) {
    for (const auto &container_2nd : container)
        for (const auto &v : container_2nd)
            cout << v << endl;
}

Passing Function to Class Template Argument

// Need partial specialization for this to work
template <typename T>
struct Logger;

// Return type and argument list
template <typename R, typename... Args>
struct Logger<R(Args...)> {
    function<R(Args...)>    m_func;
    string                  m_name;
    Logger(function<R(Args...)> f, const string &n) : m_func{f}, m_name{n} { }
 
    R operator()(Args... args) {
        cout << "Entering " << m_name << endl;
        R result = m_func(args...);
        cout << "Exiting " << m_name << endl;
        return result;
    }
};

template <typename R, typename... Args>
auto make_logger(R (*func)(Args...), const string &name) {
    return Logger<R(Args...)>(function<R(Args...)>(func), name);
}

double add(double a, double b) { return a + b; }

int main() {
    auto logged_add = make_logger(add, "Add");
    auto result = logged_add(2, 3);
    return EXIT_SUCCESS;
}

Conclusion

I hope I have covered most of the topics around C++ Template. And yes, this was a very long & intense article. But I bet you that if you do master the C++ template well, it will really give you an edge. And also open a door to sub-world of C++ i.e. template meta-programming.