Function Templates

Understand the fundamentals of C++ function templates and harness generics for more modular, adaptable code.
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Illustration representing computer hardware
Ryan McCombe
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;
}

Template Parameters vs Function Parameters

What we mean by "parameters" can get quite confusing when working with template functions, as we have two sets of parameters.

  • The parameters used to instantiate our template to create a function
  • The parameters those created functions use

We can identify a list of template parameters by the < and > that surrounds them, whilst function parameters use ( and ).

In the following example, T is a template parameter with a type of typename, whilst x and y are function parameters with a type of T:

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

Similarly, when using our template, we have both template arguments and function arguments.

Remember, a parameter is a variable within our template or function. An argument is the value we pass into that variable when we invoke our template or function.

We can similarly distinguish template arguments from function arguments based on whether they’re within <> or ()

In the following code, we use int as a template argument, whilst 1 and 2 are function arguments:

Average<int>(1, 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;
}

Preview: Trailing Return Types

Earlier in this chapter, we introduced the decltype specifier. This would also allow us to determine what type (x + y) / 2 results in, without us needing to know the types of x or y in advance:

template <typename T1, typename T2>
auto Average(T1 x, T2 y) {
  using ReturnType = decltype((x + y) / 2);

  return (x + y) / 2;
}

We can use a decltype specifier as the return type of a function, but using the traditional function syntax, it won’t have access to the function parameters. This is because the parameter list comes after the return type:

template <typename T1, typename T2>
decltype((x + y) / 2) Average(T1 x, T2 y) {
  return (x + y) / 2;
}
error C3861: 'x': identifier not found
error C3861: 'y': identifier not found

C++ provides an alternative way to specify the return type of functions, to solve exactly this use case. We place auto in the traditional return type position.

Then, we insert a trailing return type after the parameter list, separated by an arrow ->. It looks like this:

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

We cover trailing return types in a dedicated lesson later in the course:

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

Was this lesson useful?

Next Lesson

Member Function Templates

Learn how to create and use member function templates in classes and structs, including syntax, instantiation, and advanced techniques
Illustration representing computer hardware
Ryan McCombe
Ryan McCombe
Updated
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Templates
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 124 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Member Function Templates

Learn how to create and use member function templates in classes and structs, including syntax, instantiation, and advanced techniques
Illustration representing computer hardware
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved