Function_templates
Function Templates
Section titled “Function Templates”Function templates enable writing generic, reusable code that works with any data type. This chapter covers everything about function templates.
Introduction
Section titled “Introduction”Templates allow you to write functions that work with different types without code duplication. The compiler generates specific versions (instantiations) for each type used.
// Without templates - must duplicateint max(int a, int b) { return a > b ? a : b; }double max(double a, double b) { return a > b ? a : b; }
// With templates - single definitiontemplate<typename T>T max(T a, T b) { return a > b ? a : b; }Basic Syntax
Section titled “Basic Syntax”Single Type Parameter
Section titled “Single Type Parameter”#include <iostream>#include <string>
template<typename T>T maximum(T a, T b) { return (a > b) ? a : b;}
int main() { // Compiler generates: maximum(int, int) std::cout << maximum(5, 3) << "\n"; // 5
// Compiler generates: maximum(double, double) std::cout << maximum(3.14, 2.71) << "\n"; // 3.14
// Compiler generates: maximum(std::string, std::string) std::cout << maximum(std::string("apple"), std::string("banana")) << "\n"; // banana
return 0;}Multiple Type Parameters
Section titled “Multiple Type Parameters”#include <iostream>
template<typename T, typename U>std::pair<T, U> makePair(T first, U second) { return {first, second};}
int main() { auto p1 = makePair(1, 3.14); // std::pair<int, double> auto p2 = makePair("hello", 42); // std::pair<const char*, int>
std::cout << p1.first << ", " << p1.second << "\n"; std::cout << p2.first << ", " << p2.second << "\n";
return 0;}Template Parameters
Section titled “Template Parameters”Type Parameters
Section titled “Type Parameters”// Using typenametemplate<typename T>T add(T a, T b) { return a + b; }
// Using class (same as typename)template<class T>T multiply(T a, T b) { return a * b; }
// Both are equivalent - typename is preferredNon-Type Parameters
Section titled “Non-Type Parameters”#include <iostream>
// Integer non-type parametertemplate<int N>constexpr int squared() { return N * N;}
// Array size as non-type parametertemplate<size_t N>void printArray(const int (&arr)[N]) { for (size_t i = 0; i < N; i++) { std::cout << arr[i] << " "; } std::cout << "\n";}
// Pointer as non-type parametertemplate<const char* P>void printString() { std::cout << P << "\n";}
int main() { std::cout << squared<5>() << "\n"; // 25 std::cout << squared<10>() << "\n"; // 100
int arr[] = {1, 2, 3, 4, 5}; printArray(arr);
return 0;}Template Template Parameters
Section titled “Template Template Parameters”#include <vector>#include <list>#include <deque>
// Template template parametertemplate<typename T, template<typename> class Container>class Wrapper { Container<T> data_;public: void add(const T& value) { data_.push_back(value); } size_t size() const { return data_.size(); }};
int main() { Wrapper<int, std::vector> vw; vw.add(1); vw.add(2);
Wrapper<int, std::list> lw; lw.add(1); lw.add(2);
return 0;}Default Template Arguments
Section titled “Default Template Arguments”#include <iostream>#include <memory>
// Default type parametertemplate<typename T = int>T defaultValue() { return T{};}
// Multiple defaultstemplate<typename T = int, typename U = double>std::pair<T, U> makePair(U second = U{}) { return {T{}, second};}
// Default template template parametertemplate<typename T, template<typename> class Container = std::vector>class ContainerWrapper { Container<T> data_;public: void add(const T& value) { data_.push_back(value); }};
int main() { std::cout << defaultValue() << "\n"; // 0 std::cout << defaultValue<double>() << "\n"; // 0
auto p = makePair(3.14); std::cout << p.first << ", " << p.second << "\n"; // 0, 3.14
return 0;}Argument Deduction
Section titled “Argument Deduction”Automatic Deduction
Section titled “Automatic Deduction”#include <iostream>#include <string>
template<typename T>void print(T value) { std::cout << value << "\n";}
template<typename T, typename U>void printPair(T first, U second) { std::cout << first << ", " << second << "\n";}
int main() { print(42); // T = int print(3.14); // T = double print("hello"); // T = const char*
printPair(1, 2.5); // T = int, U = double
return 0;}Explicit Arguments
Section titled “Explicit Arguments”#include <iostream>
template<typename T>T convert(const char* str) { // Simple conversion (use std::stoi/stod in real code) return static_cast<T>(str[0] - '0'); // Simplified}
int main() { // Explicit template argument int x = convert<int>("42"); double y = convert<double>("3.14");
std::cout << x << "\n" << y << "\n";
return 0;}Deduction Guides (C++17)
Section titled “Deduction Guides (C++17)”#include <iostream>#include <vector>#include <memory>
// Deduction guide for pairtemplate<typename T>pair(T, T) -> pair<T, T>;
// Deduction guide for custom classtemplate<typename T>MyContainer(std::initializer_list<T>) -> MyContainer<T>;
int main() { // Automatically deduces std::pair<int, int> auto p = std::make_pair(1, 2);
return 0;}Function Template Overloading
Section titled “Function Template Overloading”#include <iostream>
// Primary templatetemplate<typename T>void process(T value) { std::cout << "Primary: " << value << "\n";}
// Overload for pointerstemplate<typename T>void process(T* value) { std::cout << "Pointer: " << *value << "\n";}
// Overload for const pointerstemplate<typename T>void process(const T* value) { std::cout << "Const pointer: " << *value << "\n";}
// Specialization for inttemplate<>void process<int>(int value) { std::cout << "Specialized int: " << value << "\n";}
int main() { int x = 42; process(42); // Specialized int process(&x); // Pointer process("hello"); // Primary (const char*)
return 0;}constexpr Functions
Section titled “constexpr Functions”Compile-Time Evaluation
Section titled “Compile-Time Evaluation”#include <iostream>
// constexpr function - evaluated at compile timeconstexpr int factorial(int n) { return n <= 1 ? 1 : n * factorial(n - 1);}
// Can be used in static_assertstatic_assert(factorial(5) == 120);static_assert(factorial(0) == 1);
int main() { // Compile-time constexpr int f5 = factorial(5); std::cout << f5 << "\n"; // 120
// Runtime int n = 5; std::cout << factorial(n) << "\n"; // Also 120
return 0;}If constexpr (C++17)
Section titled “If constexpr (C++17)”#include <iostream>#include <type_traits>
template<typename T>void processValue(T value) { if constexpr (std::is_integral_v<T>) { std::cout << "Integer: " << value * 2 << "\n"; } else if constexpr (std::is_floating_point_v<T>) { std::cout << "Float: " << value * 2.0 << "\n"; } else { // This branch is discarded for integral and floating types std::cout << "Other type\n"; }}
int main() { processValue(42); // Integer: 84 processValue(3.14); // Float: 6.28 processValue("hello"); // Other type
return 0;}SFINAE - Substitution Failure Is Not An Error
Section titled “SFINAE - Substitution Failure Is Not An Error”enable_if
Section titled “enable_if”#include <iostream>#include <type_traits>
// Only enable for integral typestemplate<typename T>std::enable_if_t<std::is_integral_v<T>, T>double twice(T value) { return value * 2.0;}
// Only enable for floating pointtemplate<typename T>std::enable_if_t<std::is_floating_point_v<T>, T>double twice(T value) { return value * 2.0;}
int main() { std::cout << twice(21) << "\n"; // 42 std::cout << twice(3.14) << "\n"; // 6.28
// Won't compile: twice("hello")
return 0;}Concepts (C++20)
Section titled “Concepts (C++20)”#include <iostream>#include <concepts>
// Using conceptstemplate<std::integral T>T doubleValue(T value) { return value * 2;}
template<std::floating_point T>T doubleValue(T value) { return value * 2.0;}
int main() { std::cout << doubleValue(21) << "\n"; std::cout << doubleValue(3.14) << "\n";
return 0;}Friend Function Templates
Section titled “Friend Function Templates”#include <iostream>
template<typename T>class Wrapper { T value_;public: Wrapper(T v) : value_(v) {}
// Friend function template template<typename U> friend Wrapper<std::common_type_t<T, U>> operator+(const Wrapper<T>& a, const Wrapper<U>& b) { return Wrapper<std::common_type_t<T, U>>( a.value_ + b.value_ ); }};
int main() { Wrapper<int> a(5); Wrapper<double> b(3.14);
auto c = a + b; // Wrapper<double> std::cout << "hello\n";
return 0;}Practical Examples
Section titled “Practical Examples”Generic Swap
Section titled “Generic Swap”#include <iostream>#include <string>
template<typename T>void swap(T& a, T& b) { T temp = std::move(a); a = std::move(b); b = std::move(temp);}
int main() { int x = 1, y = 2; swap(x, y); std::cout << x << " " << y << "\n"; // 2 1
std::string s1 = "hello", s2 = "world"; swap(s1, s2); std::cout << s1 << " " << s2 << "\n"; // world hello
return 0;}Generic Min/Max
Section titled “Generic Min/Max”#include <iostream>#include <algorithm>
template<typename T>const T& minimum(const T& a, const T& b) { return (a < b) ? a : b;}
template<typename T>const T& maximum(const T& a, const T& b) { return (a > b) ? a : b;}
template<typename T>std::pair<const T&, const T&> minMax(const T& a, const T& b) { return (a < b) ? std::pair(a, b) : std::pair(b, a);}
int main() { std::cout << minimum(3, 7) << "\n"; // 3 std::cout << maximum(3, 7) << "\n"; // 7
auto [min, max] = minMax(10, 5); std::cout << min << " " << max << "\n"; // 5 10
return 0;}Key Takeaways
Section titled “Key Takeaways”- Function templates enable generic programming
- Use
typenamefor type parameters - Support multiple template parameters
- Non-type parameters for compile-time values
- Automatic argument deduction in most cases
- Use
constexprfor compile-time computation - Concepts (C++20) provide cleaner constraints
- Friend function templates enable operator overloading across types