Lesson 11 of 13
Templates & Compile-Time Generics
Learn to write templates, which let the generate a version of a or for each type you use, with no runtime cost.
#include <iostream>
#include <vector>
#include <string>
// Function template
template <typename T>
T maxOf(const T& a, const T& b) {
return a > b ? a : b;
}
// Multiple parameters, deduced return type
template <typename A, typename B>
auto add(const A& a, const B& b) -> decltype(a + b) {
return a + b;
}
// Class template
template <typename T, std::size_t N>
class FixedArray {
T data_[N]{};
public:
T& operator[](std::size_t i) { return data_[i]; }
const T& operator[](std::size_t i) const { return data_[i]; }
constexpr std::size_t size() const { return N; }
T* begin() { return data_; }
T* end() { return data_ + N; }
};
int main() {
std::cout << maxOf(3, 7) << "\n"; // int version
std::cout << maxOf(std::string("a"), std::string("b")) << "\n"; // string version
std::cout << add(1, 2.5) << "\n"; // 3.5
FixedArray<int, 4> arr;
arr[0] = 10;
for (int v : arr) std::cout << v << ' ';
}#include <concepts>
#include <iostream>
#include <vector>
// C++20 concepts: constrain templates and get readable error messages.
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template <Numeric T>
T mean(const std::vector<T>& values) {
T total{};
for (const T& v : values) total += v;
return values.empty() ? T{} : total / static_cast<T>(values.size());
}
// Custom concept: "anything I can print"
template <typename T>
concept Printable = requires(T t, std::ostream& os) {
{ os << t } -> std::same_as<std::ostream&>;
};
template <Printable T>
void print(const T& value) { std::cout << value << '\n'; }
// Variadic templates: any number of arguments
template <typename... Args>
void printAll(const Args&... args) {
((std::cout << args << ' '), ...); // C++17 fold expression
std::cout << '\n';
}
int main() {
std::cout << mean(std::vector<double>{1.0, 2.0, 4.0}) << "\n"; // 2.33
printAll(1, "two", 3.0, 'x');
// mean(std::vector<std::string>{}); // clear error: does not satisfy Numeric
}#include <iostream>
#include <type_traits>
// Compile-time computation with constexpr
constexpr long long factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
static_assert(factorial(5) == 120); // checked by the COMPILER
// if constexpr: branches removed at compile time
template <typename T>
std::string describe(const T& value) {
if constexpr (std::is_integral_v<T>) {
return "integer: " + std::to_string(value);
} else if constexpr (std::is_floating_point_v<T>) {
return "float: " + std::to_string(value);
} else {
return "some other type";
}
}
// Template specialization: a custom implementation for one type
template <typename T> struct TypeName { static constexpr auto value = "unknown"; };
template <> struct TypeName<int> { static constexpr auto value = "int"; };
template <> struct TypeName<double> { static constexpr auto value = "double"; };
int main() {
std::cout << describe(42) << "\n";
std::cout << describe(3.14) << "\n";
std::cout << TypeName<int>::value << "\n";
constexpr auto f = factorial(10); // computed during compilation
std::cout << f << "\n";
}Finished reading?Mark it done to track your progress.
Tip: use the ← and → keys to move between lessons.