REW

How To Declare A Template Function?

Published Aug 29, 2025 5 min read
On this page

To declare a function template, you use the template keyword followed by a list of template parameters enclosed in angle brackets (<>), and then the function's signature. The template parameters act as placeholders for the types or values that will be specified later when the template is used.

Basic syntax

The fundamental syntax for a function template is as follows:

template <typename T>
return_type function_name(T parameter1, ...);

Use code with caution.

  • template: A required keyword indicating that a template definition is about to follow.
  • <typename T>: The template parameter list. typename (or class) is a keyword that specifies T is a type placeholder. You can use any valid identifier for the placeholder, but T is a common convention.
  • return_type function_name(T parameter1, ...): The standard function declaration, but with the generic type T used for one or more parameters or the return type.

Example: A simple max function

A classic example is a function that returns the larger of two values. Without templates, you would need to overload the function for each data type you want to support (e.g., int, float, double).

// Non-template function overloads
int max(int a, int b) { return (a > b) ? a : b; }
double max(double a, double b) { return (a > b) ? a : b; }

Use code with caution.

Using a function template, you can achieve the same result with a single, generic function definition:

// Template function
template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

Use code with caution.

How it works

When you call the template function, the compiler automatically generates a concrete function for the data types you pass. This process is called template instantiation.

#include <iostream>
int main() {
    // The compiler instantiates max(int, int)
    std::cout << max(10, 20) << std::endl; // Prints 20
    // The compiler instantiates max(double, double)
    std::cout << max(10.5, 20.5) << std::endl; // Prints 20.5
    // The compiler instantiates max(char, char)
    std::cout << max('a', 'z') << std::endl; // Prints 'z'
}

Use code with caution.

Advanced template declaration

Multiple template parameters

You can declare multiple template parameters to handle functions that operate on different types simultaneously.

template <typename T, typename U>
void print_pair(T first, U second) {
    std::cout << "First: " << first << ", Second: " << second << std::endl;
}
// Example usage
print_pair(42, "hello"); // T is int, U is const char*

Use code with caution.

Non-type template parameters

Templates can also take constant values as parameters, which must be known at compile time. This is useful for creating generic types with fixed sizes, like std::array.

template <typename T, size_t Size>
void print_array(const T (&arr)[Size]) {
    for (size_t i = 0; i < Size; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
}
int main() {
    int arr[] = {1, 2, 3, 4};
    print_array(arr); // Size is deduced as 4
}

Use code with caution.

Default template arguments

Default values can be provided for template parameters, similar to regular function parameters.

template <typename T = int>
T sum_two(T a, T b) {
    return a + b;
}
// Example usage
sum_two(5, 10);      // Uses default T=int
sum_two<double>(5.5, 10.5); // Explicitly specifies T=double

Use code with caution.

Important considerations

One-Definition Rule (ODR) and template implementation

A frequent point of confusion is where to place template definitions. Unlike regular functions, which are often declared in a header file (.h) and defined in a source file (.cpp), templates must be available to the compiler at the point of instantiation.

This means the full template definition (declaration and implementation) must be placed in a header file. The linker needs access to this definition to generate the function for each concrete type, and separating them across files can lead to linker errors.

my_template.h

#ifndef MY_TEMPLATE_H
#define MY_TEMPLATE_H
template <typename T>
T add(T a, T b) {
    return a + b;
}
#endif

Use code with caution.

Explicit instantiation

In some situations, the template definition can be placed in a source file to reduce compile time. This is possible through explicit instantiation, where the compiler is instructed to generate specific versions of the template.

my_template.h

#ifndef MY_TEMPLATE_H
#define MY_TEMPLATE_H
template <typename T>
T add(T a, T b); // Declaration only
#endif

Use code with caution.

my_template.cpp

#include "my_template.h"
template <typename T>
T add(T a, T b) {
    return a + b;
}
// Explicitly instantiate the template for int and double
template int add<int>(int, int);
template double add<double>(double, double);

Use code with caution.

If add is called with a type not explicitly instantiated, a linker error will result.

Template specialization

Template specialization allows you to define a specific, non-templated implementation for a particular data type. This is helpful when the generic version doesn't handle a specific type correctly or optimally.

// Generic template
template <typename T>
void print(T value) {
    std::cout << "Generic print: " << value << std::endl;
}
// Explicit specialization for const char* (C-style strings)
template <>
void print<const char*>(const char* value) {
    std::cout << "Specialized for C-string: " << value << std::endl;
}
int main() {
    print(123);         // Calls generic template
    print("hello");     // Calls specialized version
}

Use code with caution.

Concepts (C++20)

C++20 introduced concepts for more robust and readable code. Concepts let you enforce constraints on template parameters, which improves type safety and provides clearer error messages when a type doesn't meet the requirements.

#include <concepts>
#include <iostream>
// The `std::totally_ordered` concept requires that a type has comparison operators
template <std::totally_ordered T>
T max_ordered(T a, T b) {
    return (a > b) ? a : b;
}
struct Unordered {};
int main() {
    std::cout << max_ordered(10, 20) << std::endl; // OK
    // max_ordered(Unordered{}, Unordered{}); // Fails to compile, with clear error
}

Use code with caution.

Enjoyed this article? Share it with a friend.