Listen to this story
Software Developer⌨, Fitness Freak🏋, Geek🤓, Hipster🕴, Blogger👨💻, Productivity Hacker⌚
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.
template <typename T1, typename T2>
class pair {
public:
T1 first;
T2 second;
};
pair<int, char> p1;
pair<float, float> p2;
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.
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.
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
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
template <class T, size_t N = 10>
struct array {
T arr[N];
};
array<int> arr;
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){
// . . .
}
//...
pair p4{1, 'A'}; // Not OK until C++17: Can't deduce type in initialization
//...
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 <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&)
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?
template <typename T>
T sqrt(T t) { /* Some generic implementation */ }
template<>
int sqrt<int>(int i) { /* Highly optimized integer implementation */ }
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>
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
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
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);
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
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);
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
To understand variadic class template, consider use case 2 above i.e. Tuple<int, char, string> t2(1, 'a', "ABC");
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.
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?
template <typename... Args>
void print(Args &&... args) {
(void(cout << std::forward<Args>(args) << endl), ...);
}
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
};
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);
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;
void print(auto &c) { /*. . .*/ }
// Equivalent to
template <typename T>
void print(T &c) { /*. . .*/ }
template <typename T>
void f(std::vector<T>& vec) {
//. . .
}
auto f = []<typename T>(std::vector<T>& vec) {
// . . .
};
std::vector<int> v;
f(v);
#pragma once
template <typename T>
class value {
T val;
public:
T get_value();
};
#include "value.hpp"
template <typename T>
T value<T>::get_value() {
return val;
}
#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
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`
template <typename C>
void print_container(const C &container) {
for (const auto &v : container)
cout << v << endl;
}
template<
typename C,
typename T = typename C::value_type
>
void print_container(const C &container) {
for (const T &v : container)
cout << v << endl;
}
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;
}
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);
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;
}
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;
}
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;
}
// 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;
}
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.
Previously published at http://www.vishalchovatiya.com/c-template-a-quick-uptodate-look/