Function Templates

Understand the fundamentals of C++ function templates and harness generics for more modular, adaptable code.

Ryan McCombe
Updated

In previous lessons, we've seen how class templates are recipes that the compiler can use to create classes. In this lesson, we'll see how these same principles apply to function templates. We will learn:

  • Why function templates are needed to create generic functions that work with different types
  • How to create function templates by specifying template parameters, and then instantiate them by providing template arguments
  • How template argument deduction allows the compiler to infer template arguments
  • How to use auto return types and abbreviated function template syntax (C++20)

Why we Need Function Templates

Let's imagine we are creating a library of useful functions to help our team. We start with a basic utility that helps us calculate the average of two numbers.

This function behaves like we expect when working with int objects:

#include <iostream>

int Average(int x, int y) {
  return (x + y) / 2;
}

int main() {
  std::cout << "Average: " << Average(2, 4);
}
Average: 3

But if someone tries to use it with floats, they'll get the wrong answer:

#include <iostream>

int Average(int x, int y) {
  return (x + y) / 2;
}

int main() {
  std::cout << "Average: "
    << Average(1.9f, 1.5f);
}
Average: 1

Our floats both get converted to the integer 1, so our function incorrectly reports the average of 1.9, and 1.5 is 1.

We could fix this by creating another version of this function that accepts float values. But what if someone passes a double, or long, or a completely new type they've created?

Creating Function Templates

As we're likely predicting from previous lessons, the solution here involves using a function template. As with our class and variable examples, we create a function template by specifying a template parameter list, and then use those parameters within our template.

Below, we create a template that takes a single typename parameter, which we've called T. That typename is used in three places in our template function - it is used as the return type, and the type of both of our parameters:

template <typename T>
T Average(T x, T y) {
  return (x + y) / 2;
}

Using Function Templates

Now that we've defined a function template, we can instantiate this template as needed to create a function at compile time.

We provide the template argument, and the compiler instantiates our template to create a function. It substitutes the argument we provided into every location in our template where we used the parameter.

Let's imagine we invoke the template, using an expression like Average<int>:

template <typename T>
T Average(T x, T y) {
  return (x + y) / 2;
}

int main() {
  Average<int>;
}

Behind the scenes, we can imagine the compiler generating a function. Within this instance of the Average template, everywhere T appeared will be replaced with int, yielding a function that looks like this:

int Average(int x, int y) {
  return (x + y) / 2;
}

We can immediately invoke the function returned by our template using the () syntax, and providing arguments if needed. Here are some examples:

template <typename T>
T Average(T x, T y) {
  return (x + y) / 2;
}

int main() {
  Average<int>(3, 5);          // 4 
  Average<int>(1.9f, 1.5f);    // 1 
  Average<float>(1.9f, 1.5f);  // 1.7f 
  Average<int>(3, 4);          // 3 
  Average<float>(3, 4);        // 3.5f 
}

Template Argument Deduction

It's not always necessary to specify which version of a templated function we want to use. For example, instead of writing this:

Average<int>(3, 5);

We can simplify it to this:

Average(3, 5);

The compiler can see that the function created by our template is eventually going to be called with 2 integers, so it can infer what template arguments are needed to support that. As such, it will instantiate an Average<int> in this example.

If we call our function with two different types, the compiler can't deduce what it needs to do:

Average(3, 5.0f);

We could explicitly state which argument we want to instantiate the template with:

Average<float>(3, 5.0f);

Or we could conform our argument types such that template argument deduction can work. Below, both arguments have the float type, so the compiler will use the Average<float> function:

Average(float(3), 5.0f);

Multiple Template Parameters

As with other template types, function templates can accept multiple parameters, separated by commas. Below, we've updated our template such that x and y can have different types.

We've set the return type to T1 (matching x) for now, but we'll see better approaches later in this lesson:

template <typename T1, typename T2>
T1 Average(T1 x, T2 y) {
  return (x + y) / 2;
}

When instantiating our template, we also separate multiple arguments using commas. Below, we instantiate our template by passing int and float as explicit arguments:

Average<int, float>(3, 5.0f);

Or equivalently, using template argument deduction:

Average(3, 5.0f);

Default Parameters

Template parameters can have default values, making them optional arguments. Below, we give the user the option of providing T2 but if they don't, it will be int:

template <typename T1, typename T2 = int>
T1 Average(T1 x, T2 y) {
  return (x + y) / 2;
}

int main() {
  Average<int>;    // Average<int, int>
  Average<float>;  // Average<float, int>
}

The default values can also be values that appeared earlier in the argument list. Below, we give the user the option of providing T2 but if they don't, it will use the same value as T1

template <typename T1, typename T2 = T1>
T1 Average(T1 x, T2 y) {
  return (x + y) / 2;
}

int main() {
  Average<int, float>; // Average<int, float>
  Average<int>;        // Average<int, int>
  Average<float>;      // Average<float, float>
}

If we want to instantiate a template using all the default parameters, we can either provide an empty argument list using <>, or omit it entirely:

template <typename T1 = int, typename T2 = T1>
T1 Average(T1 x, T2 y) {
  return (x + y) / 2;
}

int main() {
  Average<>(1, 2);  // Average<int, int> 
  Average(1, 2);    // Average<int, int> 
}

Automatic Return Types

In the previous examples, we set the return type of our function template to be the same as the first template argument, T1.

But, this is not entirely desirable. The return type of (x + y) / 2 is not necessarily going to be the same as the type of x.

Function templates are one of the main scenarios where we will use the auto return type, letting the compiler automatically determine what type each of our template instantiations will return:

template <typename T1, typename T2>
auto Average(T1 x, T2 y) {
  return (x + y) / 2;
}

Abbreviated Function Templates

When our function is only using our template parameters within its function parameter list, there is a simpler syntax we can use.

It involves using the auto keyword as our function parameter types. For example, instead of this:

template <typename T1, typename T2>
auto Average(T1 x, T2 y) {
  return (x + y) / 2;
}

We could write this:

auto Average(auto x, auto y) {
  return (x + y) / 2;
}

This is called an abbreviated function template and it is a relatively new addition to the language, added in C++20

Summary

In this lesson, we covered function templates. The key takeaways are:

  • Function templates allow creating generic functions that work with different types
  • Template parameters are specified in <> and used within the template body
  • Template arguments are provided when using the function template to instantiate a specific version
  • The compiler can deduce template arguments based on the types of the function arguments
  • Function templates can have multiple parameters and default arguments
  • The auto keyword can ask the compiler to determine the return type, and is particularly useful when working with function templates
  • Abbreviated function template syntax provides a shorthand in C++20
Next Lesson
Lesson 26 of 128

Member Function Templates

Learn how to create and use member function templates in classes and structs, including syntax, instantiation, and advanced techniques

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Common Errors with Function Templates
Help me fix compile-time errors encountered when using function templates
How Template Argument Deduction Works
Fixing "no matching function call" errors when using function templates.
When to Use Function Templates
Why do we need function templates? When are they useful?
Function Templates vs Function Overloading
What is the difference between using a function template and overloading a function?
Template Specialization for Function Templates
How can my template function have different behaviour based on the template arguments?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant