Template Specialization

A practical guide to template specialization in C++ covering full and partial specialization, and the scenarios where they’re useful
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

Once we have a template class or function, we can create specialized versions of that template. These specializations allow us to provide a different implementation for a template, based on the exact template arguments received.

These different implementations can include behavioral changes, performance optimizations, and more.

Template specialization gives us a great deal of additional flexibility. It is also a common technique that is used to make our types compatible with functionality implemented in libraries, including the standard library. We’ll see an example of this later in the lesson

Function Template Specialization

In the following example, we have three types - Tree, Rock, and Monster. We’d like to create a generic algorithm that works with all these types - for example, to render them to the screen.

Given they have different types, we’d need to create a function template to support this:

#include <iostream>

struct Tree {
  std::string Description{"Tree"};
};

struct Rock {
  std::string Description{"Rock"};
};

struct Monster {
  std::string Name{"Monster"};
};

template <typename T>
void Render(T Object) {
  std::cout << "Rendering a "
    << Object.Description << '\n';
}

int main() {
  Render(Tree{});
  Render(Rock{});
  Render(Monster{}); 
}

Unfortunately, our Monster type is slightly different from the others. Rather than having a Description, its member variable is called Name. Accordingly, we get a compilation error when our template generates a function that tries to access a member that doesn’t exist when T is Monster:

error C2039: 'Description': is not a member of 'Monster'

To get around this, we can specialize our function template such that, if its template argument is a Monster, the function it generates has a different implementation. It looks like this:

#include <iostream>

struct Tree; struct Rock; struct Monster; // General Template template <typename T> void Render(T Object) { std::cout << "Rendering a " << Object.Description << '\n'; } // Specialized Template template <> void Render<Monster>(Monster Object) { std::cout << "Rendering a " << Object.Name << '\n'; } int main() { Render(Tree{}); Render(Rock{}); Render(Monster{}); }
Rendering a Tree
Rendering a Rock
Rendering a Monster

Preview: Type Traits and Concepts

Template specialization deals with providing entirely different implements of our templates based on their parameters. Often, this is unnecessary.

Simple cases can often be solved by adding conditional logic to our generic template. rather than providing a complete, alternate implementation through specialization.

For example, the previous problem could be solved by adding an if constexpr statement to our function template, as below:

#include <iostream>

struct Tree; struct Rock; struct Monster; template <typename T> void Render(T Object) { if constexpr (std::same_as<T, Monster>) { std::cout << "Rendering a " << Object.Name << '\n'; } else { std::cout << "Rendering a " << Object.Description << '\n'; } } int main() { Render<Tree>(Tree{}); Render<Rock>(Rock{}); Render<Monster>(Monster{}); }
Rendering a Tree
Rendering a Rock
Rendering a Monster

We cover if constexpr, type traits and concepts like std::same_as in dedicated lessons later in this chapter.

Member Function Template Specialization

Specializing member function templates uses a very similar syntax to specializing free function templates. Our following example is very similar to the previous one, except we’ve moved the Render template within a Renderer class:

#include <iostream>

struct Tree; struct Rock; struct Monster; class Renderer { public: // General template template <typename T> void Render(T Object) { std::cout << "Rendering " << Object.Description << '\n'; }; // Specialized template template <> void Render<Monster>(Monster Object) { std::cout << "Rendering " << Object.Name << '\n'; } }; int main() { Renderer A; A.Render(Tree{}); A.Render(Rock{}); A.Render(Monster{}); }
Rendering A Tree
Rendering A Rock
Rendering A Monster

We also have the option of overloading a member function outside of the class declaration, if preferred. This is the most common technique in large projects, as it allows us to provide the overload alongside the type it is associated with.

For example, we can imagine Renderer being a core system in our project, whilst Monster is a type we want to make compatible with it via template specialization. In this scenario, we’d prefer the specialization be in the same files that define our Monster type. We can implement that as follows:

// Monster.h
#include <string>
#include "Renderer.h"

struct Monster {
  std::string Name{"A Monster"};
};

template <>
void Renderer::Render<Monster>(Monster Object) {
  // Monster-specific rendering implementation
}

Class Template Specialization

Class template specialization has the same use cases and syntax as the other forms of specialization we have seen. Below, we’ve replicated a similar pattern as above, except now our entire class is a template, rather than just a member function:

#include <iostream>

struct Tree; struct Rock; struct Monster; // General Template template <typename T> class Renderable { public: void Render() { std::cout << "Rendering " << Object.Description << '\n'; }; T Object; }; // Specialized Template template <> class Renderable<Monster> { public: void Render() { std::cout << "Rendering " << Object.Name << '\n'; }; Monster Object; }; int main() { Renderable SomeTree{Tree{}}; SomeTree.Render(); Renderable SomeRock{Rock{}}; SomeRock.Render(); Renderable SomeMonster{Monster{}}; SomeMonster.Render(); }
Rendering A Tree
Rendering A Rock
Rendering A Monster

We can alternatively provide member functions for a specific instantiation of our class template, without providing a replacement for the entire class.

In the following example, we have also moved the general definition of Render outside the class body for clarity, but this is optional:

#include <iostream>

struct Tree; struct Rock; struct Monster; template <typename T> class Renderable { public: void Render(); T Object; }; // General implementation of Render template <typename T> void Renderable<T>::Render() { std::cout << "Rendering " << Object.Description << '\n'; } // Specialized implementation of Render template <> void Renderable<Monster>::Render() { std::cout << "Rendering " << Object.Name << '\n'; } int main() { Renderable SomeTree{Tree{}}; SomeTree.Render(); Renderable SomeRock{Rock{}}; SomeRock.Render(); Renderable SomeMonster{Monster{}}; SomeMonster.Render(); }
Rendering A Tree
Rendering A Rock
Rendering A Monster

Partial Specialization

Our previous examples of class template specialization specified the values of every argument in the template parameter list.

We can determine when this is the case because our parameter list will be empty:

template <>
void Renderable<Monster>::Render() {
  // ...
}

These were examples of full specialization. However, when we specialize a template, we don’t need to specify every template parameter. We can specify only a subset of them, using a technique called partial template specialization.

In this example, we’ve updated our Renderable class template to have two parameters - a typename and an int. Then, we’ve provided a partial specialization that specifies only one of those parameters.

As such, our specialization applies to any instantiation of our template that uses 1 as the Quantity argument, regardless of what is provided for the typename T:

#include <iostream>

struct Tree; struct Rock; struct Monster; // General template template <typename T, int Quantity> class Renderable { public: void Render() { std::cout << "Rendering " << Quantity << " " << Object.Description << "s\n"; }; T Object; }; // Specialization that is used if Quantity is 1 template <typename T> class Renderable<T, 1> { public: void Render() { std::cout << "Rendering a single " << Object.Description << "\n"; }; T Object; }; int main() { Renderable<Tree, 1> OneTree{Tree{}}; OneTree.Render(); Renderable<Rock, 1> OneRock{Rock{}}; OneRock.Render(); Renderable<Tree, 10> SomeTrees{Tree{}}; SomeTrees.Render(); }
Rendering a single Tree
Rendering a single Rock
Rendering 10 Trees

In this example, we’ve partially specialized Renderable for any instantiation that uses Monster as the argument for T, regardless of what Quantity is used:

#include <iostream>

struct Tree; struct Rock; struct Monster;
class Renderable {/*...*/}; // Specialization that is used if T is Monster template <int Quantity> class Renderable<Monster, Quantity> { public: void Render() { std::cout << "Rendering " << Quantity << " " << Object.Name << "s\n"; }; Monster Object; }; int main() { Renderable<Tree, 10> Trees{Tree{}}; Trees.Render(); Renderable<Monster, 10> Monsters{Monster{}}; Monsters.Render(); Renderable<Monster, 500> Army{Monster{}}; Army.Render(); }
Rendering 10 Trees
Rendering 10 Monsters
Rendering 500 Monsters

Of course, our specialization could provide values for both arguments. This would be another example of a full specialization:

template <>
class Renderable<Monster, 1> {
  // ...
};

Templated Member Functions of Class Template

It’s not possible to specialize the template of a member function when the class itself is also a template. However, we can get equivalent behaviour by conventional function overloading.

Our following example has a member function called Render that accepts a Monster, and a template function that accepts any type.

Based on how overload resolution happens, which we cover in detail later in the chapter, the non-template function takes priority over the templated function. This gives us the same behaviour as if we had specialized the template function:

#include <iostream>

struct Tree; struct Rock; struct Monster; template <int Quantity> class Renderer { public: template <typename T> void Render(T Object) { std::cout << "Rendering " << Quantity << " " << Object.Description << "s\n"; }; void Render(Monster Object) { std::cout << "Rendering " << Quantity << " " << Object.Name << "s\n"; }; }; int main() { Renderer<10> A; A.Render(Tree{}); A.Render(Rock{}); A.Render(Monster{}); }
Rendering 10 Trees
Rendering 10 Rocks
Rendering 10 Monsters

We cover overload resolution in more detail later in this chapter.

Below, we have moved the definition of both our template function and our member function outside of the class declaration, and included two additional examples:

#include <iostream>

struct Tree; struct Rock; struct Monster; template <int Quantity> class Renderer { public: template <typename T> void Render(T Object); void Render(Monster Object); }; // Generic implementation template <int Quantity> template <typename T> void Renderer<Quantity>::Render(T Object) { std::cout << "Rendering " << Quantity << " " << Object.Description << "s\n"; } // Specialisation for Quantity = 1 template <> template <typename T> void Renderer<1>::Render(T Object) { std::cout << "Rendering a single " << Object.Description << '\n'; } // Overload for T = Monster template <int Quantity> void Renderer<Quantity>::Render(Monster Object) { std::cout << "Rendering " << Quantity << " " << Object.Name << "s\n"; } // Specialization for Quantity = 1 // And Overloaded for T = Monster template <> void Renderer<1>::Render(Monster Object) { std::cout << "Rendering a single " << Object.Name << '\n'; } int main() { Renderer<10> A; A.Render(Tree{}); A.Render(Rock{}); A.Render(Monster{}); Renderer<1> B; B.Render(Tree{}); B.Render(Rock{}); B.Render(Monster{}); }
Rendering 10 Trees
Rendering 10 Rocks
Rendering 10 Monsters
Rendering a single Tree
Rendering a single Rock
Rendering a single Monster

Template Specialization with Libraries

One of the main practical use cases for template specialization is to enable integration between different code bases. For example, we might want to use a library of code created by some other developer.

One way they might decide to enable this integration could involve the library developer providing templates, and us specializing those templates for our types.

For example, let’s imagine we’re using a library that does some processing of an object, and part of that process requires the object’s name.

The library author doesn’t know how to get that name, because it doesn’t know what types it will be dealing with. Therefore, they provide a templated function called GetName():

// SomeLibrary.h
#pragma once
#include <iostream>

namespace SomeLibrary{
  template <typename T>
  std::string GetName(T& Obj){
    return Obj.GetName();
  }

  template <typename T>
  void Process(T& Obj){
    std::cout << "Processing " << GetName(Obj);

    //...

    std::cout << "\nDone!";
  }
}

In our code, for any type we want to be compatible with this library, we would specialize the GetName() function, telling the library how to get our objects’ names:

// Player.h
#pragma once
#include <string>
#include "SomeLibrary.h"

struct Player {
  std::string Name;
};

namespace SomeLibrary {
  template <>
  std::string GetName<Player>(Player& P) {
    return P.Name;
  }
}

Now, our objects can work elegantly with the library, without the library knowing anything about our code:

#include <SomeLibrary.h>
#include "Player.h"

int main() {
  Player PlayerOne{"Anna"};
  SomeLibrary::Process(PlayerOne);
}
Processing Anna
Done!

The standard library also uses this approach. For example:

  • to make our type compatible with string interpolation utilities like std::format() and std::print(), we’d need to specialise the std::formatter template
  • to make our type compatible with hash-based containers like std::set and std::map, we’d need to specialise the std::hash template

We’ll introduce both of these topics in detail later in the course.

Summary

This lesson covers template specialization in C++, a technique that allows creating specialized versions of function and class templates for specific types. Key takeaways include:

  • Function, member function, and class templates can be specialized to provide different implementations based on template arguments
  • Full specialization specifies values for all template parameters, while partial specialization specifies only a subset
  • Specialization is useful for integrating with libraries and making user-defined types compatible with library functionality
  • The standard library uses specialization for features like string interpolation and hash-based containers
  • Specialization provides flexibility and optimization opportunities in generic programming

Was this lesson useful?

Next Lesson

Understanding Overload Resolution

Learn how the compiler decides which function to call based on the arguments we provide.
Illustration representing computer hardware
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Template Specialization

A practical guide to template specialization in C++ covering full and partial specialization, and the scenarios where they’re useful

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

Understanding Overload Resolution

Learn how the compiler decides which function to call based on the arguments we provide.
Illustration representing computer hardware
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved