Function template in C++ metaprogramming

Introduction

In C++, a function template is a blueprint for creating functions that can operate with different data types. It allows you to write a generic function that works for various types without rewriting the code for each type.

template <typename T>  
auto f(T a) {
    // Function body
}
  • template <typename T> declares a template with a type parameter T.
  • T can be replaced by any data type (int, float, string, etc.) when the function is called.
f(1); // T is int
f<double>(1); // T is forced to be double
f("Hello"); // T is char const*
f<std::string>("Hello"); // T is forced to be std::string

If the type, T, is not needed since C++20, we can write

 
auto f(auto a) {
    // Function body
}

The we can call the function with differt type arguments as

f(1); // auto turns to int
f(2.0); // auto turns to double

In the examples above, the type is an argument type. If the type is used only locally inside a function, an explicit instantiation is necessary because it is not deducible by a compiler:

template<typename T>
auto myCast(int a){
    return (T)a; // casting a to type T
}

// Used as
int i=1;
auto x = myCast<double>(i); // explicitly saying T=double

Example

#include <iostream>
using namespace std;

// Function template to find the maximum of two values
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << getMax<int>(3, 7) << endl;       // Output: 7 (int)
    cout << getMax<double>(3.5, 2.9) << endl; // Output: 3.5 (double)
    cout << getMax<char>('a', 'z') << endl;   // Output: 'z' (char)
    
    return 0;
}
  • The compiler generates different versions of the function for each type used (template instantiation).
  • You can also use class instead of typename in the template declaration (e.g., template <class T>).

Explicit Specialization

You can specialize a template function to handle specific types differently:

template<typename T>
size_t getSize(T v){ return v.size();}

template<>
size_t getSize<std::string>(std::string s){return 1;} 

// Also we could do
// template<>
// size_t getSize(std::string s){return 1;} 

// Used as:
std::vector<int> v{1, 2, 3};
std::cout<< getSize(v); // prints 3
std::cout<< getSize(std::string{"Hello"}); // prints 1

Partial specialization ban

Partial specialization allows tailoring templates for subsets of types (e.g., all pointers). While class templates support this, function templates do not. If partial specializations were allowed, the compiler would struggle to prioritize between overloads, full specializations, and partial specializations.

template<typename T> 
void bar(T val) { /* generic */ }

// Error: partial specialization of function:
template<typename T> 
void bar<T*>(T* val) { /* for pointers */ }

Alternatives

The alternatives to function partial specializations are explained in the next sections.

Overloading

Define overloads for specific type categories:

template<typename T> void bar(T val) { /* generic */ }
template<typename T> void bar(T* val) { /* for pointers */ }

Overloading cannot be used when T is local to the function and not an argument type.

if constexpr (C++17)

Use compile-time branching for type-specific logic:

template<typename T>
void process(T val) {
    if constexpr (std::is_pointer_v<T>) {
        // Logic for pointers
    } else {
        // Generic logic
    }
}

For more on this, see my post on constexpr.

Delegation to Class Templates

Class templates support partial specialization, so we can use them for complex logic:

// Class template with partial specialization
template<typename T>
struct Processor {
    static void run(T val) { /* generic */ }
};

template<typename T>
struct Processor<T*> {
    static void run(T* val) { /* specialized for pointers */ }
};

// Function template delegates to the class
template<typename T>
void process(T val) {
    Processor<T>::run(val); // Uses partial specialization
}

Tag Dispatching

Leverage traits or tags to route logic:

template<typename T>
void process_impl(T val, std::true_type) { /* for pointers */ }

template<typename T>
void process_impl(T val, std::false_type) { /* generic */ }

template<typename T>
void process(T val) {
    process_impl(val, std::is_pointer<T>());
}

For more on this, see my post on tag dispatching.

Tags ➡ C++

Subscribe

I notify you of my new posts

Latest Posts

Comments

0 comment