Why?
What?
Recommended Reference:
C++ Templates the Complete guide (David Vandevoorde..2018)
Without generic programming, to create two logically identical functions that behave in a way that is independent to the type, we have to rely on function overloading.
#include <iostream>
auto min(int a, int b) -> int {
return a < b ? a : b;
}
auto min(double a, double b) -> double{
return a < b ? a : b;
}
struct int_list{...};
struct double_list{...};
double int_list_append(...);
double double_list_append(...);
auto main() -> int {
std::cout << min(1, 2) << "\n"; // calls min(int, int)
std::cout << min(1.0, 2.0) << "\n"; // calls min(double, double)
}
Explore how this looks in Compiler Explorer
demo701-functemp1.cpp
#include<iostream>
auto min(int a, int b) -> int {
return a < b ? a : b;
}
auto min(double a, double b) -> double{
return a < b ? a : b;
}
auto main() -> int {
std::cout << min(1, 2) << "\n"; // calls min(int, int)
std::cout << min(1.0, 2.0) << "\n"; // calls min(double, double)
}
dff
Still not producing any code: Just compile time information of a function.
#include <iostream>
template <typename T>
auto min(T a, T b) -> T {
return a < b ? a : b;
}
auto main() -> int {
std::cout << min(1, 2) << "\n"; // calls int min(int, int)
std::cout << min(1.0, 2.0) << "\n"; // calls double min(double, double)
}
Explore how this looks in Compiler Explorer
demo702-functemp2.cpp
Provide enough info to complier to generate a function body
template type parameter: a placeholder for a type argument
template <typename T>
T min(T a, T b) {
return a < b ? a : b;
}
template parameter list: a placeholder for argument expression
template <typename T>
T functionName(T parameter1, T parameter2, ...) {
// code
}
Argument substitution happens at compile time: not run time
Depending upon the context either:
#include<iostream>
int const& max (int const& a, int const& b){
std::cout<<"max(int, int)"<<std::endl;
return a < b ? b : a;
}
template <typename T> // T's scope begins here..
T const& max (T const& a, T const& b){
std::cout << "max(T, T)" << std::endl;
return a < b ? b : a;
} // T's scope ends here
template <typename T>
T const& max (T const& a, T const& b, T const& c) {
std::cout << "max(T,T, T)" << std::endl;
return max(max (a,b), c);
//max(max<> (a,b), c);
}
int main() {
max(15.0, 20.0);
max('x', 'y');
max(15,25);
max<>(15,25);
max<double>(15.0,25.0);
max(15, 20, 25);
max(15, 20, 25);
}
// create a function template that prints the swap of two numbers.
#include<iostream>
template<class T>
void swap(T &a,T &b) {
T temp = a;
a = b;
b = temp;
}
int main() {
int a = 10, b = 20;
double x = 20.3, y = 55.3;
std::cout << "Before Swap" << std::endl;
std::cout << "A=" << a << "\t" << "B=" << b << std::endl;
std::cout << "X=" << x << "\t" << "B=" << y << std::endl;
swap(a, b);
swap(x, y);
std::cout << "After Swap: "<< std::endl;
std::cout << "A=" << a << "\t" << "B=" << b << std::endl;
std::cout << "X=" << x << "\t" << "B=" << y << std::endl;
}
#include<iostream>
int const& max (int const& a, int const& b){
std::cout << "max(int, int)" << std::endl;
return a < b ? b : a;
}
template <typename T1, typename T2>
T1 const& max (T1 const& a, T2 const& b) {
std::cout << "max(T1, T2)" << std::endl;
return a < b ? b : a;
}
template <typename T1, typename T2>
T1 const& max (T1 const& a, T2 const& b, T2 const& c) {
std::cout << "max(T1,T2, T2)" << std::endl;
return max(max(a,b), c);
// max(max<>(a,b), c);
}
int main() {
max(15.0, 20.0);
max('x', 'y');
max(15,25);
max<>(15,25); // Explicit instantiation
max<double>(15.0,25.0);
max(15, 20, 25);
// max<double, double, double> (20.0, 15.0, 20.0);
}
// create a function template that prints the swap of two numbers.
#include<iostream>
template<typename T1, typename T2>
void swap(T1 &a, T2 &b) {
T2 temp = a;
a = b;
b = temp;
}
int main() {
int a = 10, b = 20;
double x = 20.3, y = 55.3;
std::cout << "Before Swap" << std::endl;
std::cout << "A=" << a << "\t" << "B=" << b << std::endl;
std::cout << "X=" << x << "\t" << "B=" << y << std::endl;
swap(a, b);
swap(x, y);
std::cout << "After Swap: "<< std::endl;
std::cout << "A=" << a << "\t" << "B=" << b << std::endl;
std::cout << "X=" << x << "\t" << "B=" << y << std::endl;
}
https://www.youtube.com/watch?v=7SqySe4Lkow&ab_channel=CppNuts
#include<iostream>
#include<sstream>
#include<vector>
template<typename T>
T add_all(const std::vector<T> &list) {
T accumulator = {};
for (auto& elem:list){
accumulator += elem;
}
return accumulator;
}
template<>
T add_all(const std::vector<std::string> &list) {
std::string accumulator = {};
for (const std::string& elem : list)
for (const char& chr : elem)
accumulator += elem;
}
return accumulator;
}
int main() {
std::vector<int> ivec = {4,3,2,4};
std::vector<double> dvec = {4.0,3.0,2.0,4.0};
std::vector<string> svec = {"abc", "bcd"};
std::cout << add_all(ivec) << std::endl;
std::cout << add_all(dvec) << std::endl;
std::cout << add_all(svec) << std::endl;
}
#include <iostream>
template <typename T>
void fun(T a) {
std::cout << "The main template fun(): "
<< a << std::endl;
}
template<> // may be skipped, but prefer overloads
void fun(int a) {
std::cout << "Explicit specialisation for int type: "
<< a << std::endl;
}
int main() {
fun<char>('a');
fun<int>(10);
fun<float>(10.14);
}
#include <array>
#include <iostream>
template<typename T, std::size_t size>
auto find_min(const std::array<T, size> &a) -> T {
T min = a[0];
for (std::size_t i = 1; i < size; ++i) {
if (a[i] < min)
min = a[i];
}
return min;
}
auto main() -> int {
std::array<int, 3> x{3, 1, 2};
std::array<double, 4> y{3.3, 1.1, 2.2, 4.4};
std::cout << "min of x = " << find_min(x) << "\n";
std::cout << "min of x = " << find_min(y) << "\n";
}
Compiler deduces T and size from a
demo703-nontype1.cpp
auto find_min(const std::array<int, 3> a) -> int {
int min = a[0];
for (int i = 1; i < 3; ++i) {
if (a[i] < min)
min = a[i];
}
return min;
}
auto find_min(const std::array<double, 4> a) -> double {
double min = a[0];
for (int i = 1; i < 4; ++i) {
if (a[i] < min)
min = a[i];
}
return min;
}
demo704-nontype2.cpp
class int_stack {
public:
auto push(int&) -> void;
auto pop() -> void;
auto top() -> int&;
auto top() const -> const int&;
private:
std::vector<int> stack_;
};
class double_stack {
public:
auto push(double&) -> void;
auto pop() -> void;
auto top() -> double&;
auto top() const -> const double&;
private:
std::vector<double> stack_;
};
class int_array {
int array[15];
public:
void initialize(int value) {
for(int i = 0; i < 15; i++) {
array[i] = value;
}
}
int& at(int index) {
return array[index];
}
};
Text
Same behaviour with double
..
..
..
..
#include <iostream>
// Class template
template <class T>
class Number {
private:
// Variable of type T
T num;
public:
Number(T n) : num(n) {} // constructor
T get_num() {
return num;
}
};
int main() {
// create object with int type
Number<int> number_int(7);
// create object with double type
Number<double> number_double(7.7);
std::cout << "int Number = " << number_int.get_num() << std::endl;
std::cout << "double Number = " << number_double.get_num() << std::endl;
return 0;
}
//template <template T> //for function
// template < parameter-list > class-declaration
template <class T>
class ClassName {
private:
T var;
... .. ...
public:
T function_name(T arg);
... .. ...
};
ClassName<dataType> class_object;
//For Example
ClassName<int> class_object;
ClassName<float> class_object;
ClassName<string> class_object;
template <class T>
class ClassName {
... .. ...
// Function prototype
ReturnType function_name();
};
// Function definition
template <class T>
returnType ClassName<T>::function_name() {
// code
}
#include <iostream>
// Class template
template <typename T>
class Number {
private:
// Variable of type T
T num;
public:
Number(T n) : num(n) {} // constructor
T get_num();
// T get_num() {
// return num;
// }
};
// Member template definition
template <typename T>
T Number<T>::get_num() {
return num;
}
int main() {
// create object with int type
Number<int> number_int(7);
// create object with double type
Number<double> number_double(7.7);
std::cout << "int Number = " << number_int.get_num() << std::endl;
std::cout << "double Number = " << number_double.get_num() << std::endl;
return 0;
}
template <class T>
class A
{
static int i;
};
template <class T>
int A<T>::i=0;
// stack.h
#ifndef STACK_H
#define STACK_H
#include <iostream>
#include <vector>
template<typename T>
class stack {
public:
friend auto operator<<(std::ostream& os, const stack& s) -> std::ostream& {
for (const auto& i : s.stack_)
os << i << " ";
return os;
}
auto push(T const& item) -> void;
auto pop() -> void;
auto top() -> T&;
auto top() const -> const T&;
auto empty() const -> bool;
private:
std::vector<T> stack_;
};
#include "./demo705-classtemp.tpp"
#endif // STACK_H
#include "./demo705-classtemp.h"
template<typename T>
auto stack<T>::push(T const& item) -> void {
stack_.push_back(item);
}
template<typename T>
auto stack<T>::pop() -> void {
stack_.pop_back();
}
template<typename T>
auto stack<T>::top() -> T& {
return stack_.back();
}
template<typename T>
auto stack<T>::top() const -> T const& {
return stack_.back();
}
template<typename T>
auto stack<T>::empty() const -> bool {
return stack_.empty();
}
demo705-classtemp-main.h
demo705-classtemp-main.tpp
#include <iostream>
#include <string>
#include "./demo705-classtemp.h"
int main() {
stack<int> s1; // int: template argument
s1.push(1);
s1.push(2);
stack<int> s2 = s1;
std::cout << s1 << s2 << '\n';
s1.pop();
s1.push(3);
std::cout << s1 << s2 << '\n';
// s1.push("hello"); // Fails to compile.
stack<std::string> string_stack;
string_stack.push("hello");
// string_stack.push(1); // Fails to compile.
}
demo705-classtemp-main.cpp
#include <iostream>
template <typename T, std::size_t length>
class Array {
T array[length];
public:
void fill(T value) {
for (int i = 0; i < length; i++)
array[i] = value;
}
// returns a reference to array element of type T@ given index
T& at(int index) {
return array[index];
}
};
int main() {
Array<int, 5> int_arr;
int_arr.fill(2);
std::cout << "int_array[4]: " << int_arr.at(4) << std::endl;
Array<std::string, 8> str_arr;
str_arr.fill("abc");
str_arr.at(6) = "123";
for (int i = 0; i < 8; i++)
std::cout << "str_arr[" << i << "]: " << str_arr.at(i) << std::endl;
return 0;
}
#include <iostream>
class int_array {
int array[10];
public:
void fill(int value) {
for (int i = 0; i < 10; i++)
array[i] = value;
}
int& at(int index) {
return array[index];
}
};
class string_array {
public:
std::string array[10];
void fill(std::string value)
{
for (int i = 0; i < 10; i++)
array[i] = value;
}
std::string& at(int index)
{
return array[index];
}
};
int main()
{
int_array<int> int_arr;
int_arr.fill(2);
std::cout << "int_array[4]: " << int_arr.at(4) << std::endl;
string_array<std::string> str_arr;
str_arr.fill("abc");
str_arr.at(6) = "123";
for (int i = 0; i < 8; i++)
std::cout << "str_arr[" << i << "]: " << str_arr.at(i) << std::endl;
return 0;
}
template <typename T>
stack<T>::stack() { }
template <typename T>
stack<T>::stack(const stack<T> &s) : stack_{s.stack_} { }
template <typename T>
stack<T>::stack(Stack<T> &&s) : stack_(std::move(s.stack_)); { }
template <typename T>
stack<T>& stack<T>::operator=(const stack<T> &s) {
stack_ = s.stack_;
}
template <typename T>
stack<T>& stack<T>::operator=(stack<T> &&s) {
stack_ = std::move(s.stack_);
}
template <typename T>
stack<T>::~stack() { }
Default rule-of-five (you don't have to implement these in this case)
The rule of 5 states that if a class has a user-declared destructor, copy constructor, copy assignment constructor, move constructor, or move assignment constructor, then it must have the other 4.
#include <iostream>
template <class T>
class Test {
// Data members of test
public:
Test() {
// Initialization of data members
cout << "General template object \n";
}
// Other methods of Test
};
template <>
class Test<int> {
public:
Test() {
// Initialization of data members
std::cout << "Class template specialisation\n";
}
};
int main() {
Test<int> a;
Test<char> b;
Test<float> c;
return 0;
}
template <typename T>
auto min(T a, T b) -> T;
template <typename T>
auto min(T a, T b) -> int {
return a < b ? a : b;
}
#include <iostream>
auto main() -> int {
std::cout << min(1, 2) << "\n";
}
min.h
min.cpp
main.cpp
template <typename T>
auto min(T a, T b) -> T {
return a < b ? a : b;
}
#include <iostream>
auto main() -> int {
std::cout << min(1, 2) << "\n";
}
min.h
main.cpp
template <typename T>
T min(T a, T b);
template <typename T>
auto min(T a, T b) -> T {
return a < b ? a : b;
}
template int min<int>(int, int);
template double min<double>(double, double);
#include <iostream>
auto main() -> int {
std::cout << min(1, 2) << "\n";
std::cout << min(1.0, 2.0) << "\n";
}
min.h
min.cpp
main.cpp
#include <vector>
template <typename T>
class stack {
public:
stack() {}
auto pop() -> void;
auto push(const T& i) -> void;
private:
std::vector<T> items_;
}
template <typename T>
auto stack<T>::pop() -> void {
items_.pop_back();
}
template <typename T>
auto stack<T>::push(const T& i) -> void {
items_.push_back(i);
}
auto main() -> int {
stack<int> s;
s.push(5);
}
stack.h
main.cpp
Each template instantiation has it's own set of static members
#include <vector>
template<typename T>
class stack {
public:
stack();
~stack();
auto push(T&) -> void;
auto pop() -> void;
auto top() -> T&;
auto top() const -> const T&;
private:
static int num_stacks_;
std::vector<T> stack_;
};
template<typename T>
int stack<T>::num_stacks_ = 0;
template<typename T>
stack<T>::stack() {
num_stacks_++;
}
template<typename T>
stack<T>::~stack() {
num_stacks_--;
}
#include <iostream>
#include "./demo706-static.h"
auto main() -> int {
stack<float> fs;
stack<int> is1, is2, is3;
std::cout << stack<float>::num_stacks_ << "\n";
std::cout << stack<int>::num_stacks_ << "\n";
}
demo706-static.h
demo706-static.cpp
#include<iostream>
template<typename T>
void print(const T &x) {
static int value = 10;
std::cout << ++value
<< std::endl;
}
int main() {
print(1);
print('x');
print(2.5);
print(2);
print(3);
}
Each stack instantiation has one unique instantiation of the friend
#include <iostream>
#include <vector>
template<typename T>
class stack {
public:
auto push(T const&) -> void;
auto pop() -> void;
friend auto operator<<(std::ostream& os, stack<T> const& s) -> std::ostream& {
return os << "My top item is " << s.stack_.back() << "\n";
}
private:
std::vector<T> stack_;
};
template<typename T>
auto stack<T>::push(T const& t) -> void {
stack_.push_back(t);
}
#include <iostream>
#include <string>
#include "./stack.h"
auto main() -> int {
stack<std::string> ss;
ss.push("Hello");
std::cout << ss << "\n":
stack<int> is;
is.push(5);
std::cout << is << "\n":
}
demo707-friend.h
demo707-friend.cpp
Compiler processes each template into two phases:
1 - When compiler reaches the definition
(happen once for each template)
2 - When compiler instantiates the template for particular combination of type arguments.
(happen once for each instantiation)
#include <iostream>
constexpr int constexpr_factorial(int n) {
return n <= 1 ? 1 : n * constexpr_factorial(n - 1);
}
int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
auto main() -> int {
// Beats a #define any day.
constexpr int max_n = 10;
constexpr int tenfactorial = constexpr_factorial(10);
// This will fail to compile
int ninefactorial = factorial(9);
std::cout << max_n << "\n";
std::cout << tenfactorial << "\n";
std::cout << ninefactorial << "\n";
}
demo708-constexpr.cpp