Templates in functions
Consider this simple template function definition
template<typename T>
T customMax(T a, T b) {
return b < a ? a : b;
}Type deduction happens at compile time like so
int main() {
std::cout << "templates\n";
std::cout << customMax(1, 2) << '\n';
// customMax<std::string>(std::string, std::string)
std::cout << customMax(std::string("hello"), std::string("world")) << '\n';
// customMax<const char*>(const char*, const char*)
std::cout << customMax("hello", "world") << '\n'; // compares addresses and not strings
// customMax<std::string>(std::string, std::string)
std::cout << customMax<std::string>("hello", "world") << '\n';
return 0;
}A simple definition like the one above comes with some non-negotiable assumptions and constraints
These are:
Tmust support<operator and must return bool when used- copy and move constructors must be defined for
T
std::complex<double> c1, c2;
std::cout << customMax(c1, c2); // ERROR: no < supported to complex<double>
std::atomic<int> a1{8}, a2{15};
std::cout << customMax(a1, a2); // ERROR: copying is disabled for atomicNOTE
Since C++ 20 we can enforce formal constraints for generic code, see concepts
Variadic templates
Type safe variadic templates for classes and functions
template<typename T, typename... Types>
void print(T firstarg, Types... args) {
std::cout << firstArg << " ";
print(args...);
}
// base case function definition
void print() {}
std::string str = "world";
print("hello", 7.5, str);Find dimension at compile time
Say we have to write metafunction to find the dimension of array of any type at compile time. Something like this
static_assert(Rank<int>::value == 0);
static_assert(Rank<float[3]>::value == 1);
static_assert(Rank<char[][3]>::value == 2);
static_assert(Rank<Emp[2][3][4][5]>::value == 4);Solution is given below
template <typename T>
struct Rank {
static constexpr size_t value = 0;
};
// int arr[] = {1, 2, 3, 4}
template <typename T>
struct Rank<T[]> {
static constexpr size_t value = 1u + Rank<T>::value;
};
//int arr[4] = {1, 2, 3, 4}
template <typename T, size_t N>
struct Rank<T[N]> {
static constexpr size_t value = 1u + Rank<T>::value;
}Constrain templates
We can allow only certain type of templates to be used using <type_traits> header.
#include <iostream>
#include <type_traits>
template <typename T>
struct is_numeric {
static constexpr bool value = std::is_integral<T>::value || std::is_floating_point<T>::value;
};
template <typename T, size_t N>
class Vector {
static_assert(is_numeric<T>::value, "Vector only supports numeric types!");
T data[N];
size_t size_ = 0;
public:
void add(T value) {
if (size_ < N) {
data[size_++] = value;
} else {
std::cout << "Vector full!\n";
}
}
T get(size_t index) const {
return data[index];
}
size_t size() const { return size_; }
template <typename Func>
void for_each(Func f) const {
for (size_t i = 0; i < size_; ++i)
f(data[i]);
}
};
int main() {
Vector<int, 5> arr;
arr.add(10);
arr.add(20);
arr.add(30);
std::cout << "Array elements: ";
arr.for_each([](int x){ std::cout << x << " "; });
std::cout << "\n";
// Uncommenting this line will fail at compile-time
// Vector<std::string, 3> badArray;
return 0;
}