Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Partially Specialized Templates
Learn how to write generic, reusable code using partial template specialization.
This lesson covers an advanced topic that provides powerful capabilities for specific scenarios. Partial template specialization allows you to specialize a template for categories of types while keeping other template parameters flexible.
The limitation of full specialization
Consider a 2D table template with both type and dimension parameters:
template <typename T, int Rows, int Cols>
class Table
{
private:
T m_cells[Rows][Cols]{};
public:
T& at(int row, int col)
{
return m_cells[row][col];
}
const T& at(int row, int col) const
{
return m_cells[row][col];
}
T* data() { return &m_cells[0][0]; }
};
Suppose you want a print function that displays tables. A generic version:
template <typename T, int Rows, int Cols>
void print(const Table<T, Rows, Cols>& table)
{
for (int r{ 0 }; r < Rows; ++r)
{
for (int c{ 0 }; c < Cols; ++c)
std::cout << table.at(r, c) << ' ';
std::cout << '\n';
}
}
This works for numeric types:
#include <iostream>
int main()
{
Table<int, 3, 4> numbers;
int value{ 1 };
for (int r{ 0 }; r < 3; ++r)
for (int c{ 0 }; c < 4; ++c)
numbers.at(r, c) = value++;
print(numbers);
return 0;
}
Output:
1 2 3 4
5 6 7 8
9 10 11 12
But for character tables, you might prefer no spaces between characters for a more compact display:
int main()
{
Table<char, 3, 10> text;
const char* msg{ "HelloWorld" };
for (int i{ 0 }; msg[i]; ++i)
text.at(0, i) = msg[i];
print(text); // Shows: H e l l o W o r l d (with spaces - not ideal)
return 0;
}
The problem with full specialization
You might try full specialization:
template <>
void print<char, 10, 3>(const Table<char, 10, 3>& table)
{
for (int r{ 0 }; r < 3; ++r)
{
for (int c{ 0 }; c < 10; ++c)
std::cout << table.at(r, c);
std::cout << '\n';
}
}
This only works for Table<char, 10, 3>. What about Table<char, 5, 2> or Table<char, 20, 4>? You would need a separate specialization for every dimension combination - impractical.
Partial template specialization
Partial template specialization solves this by letting you specialize some parameters while leaving others as templates:
#include <iostream>
template <typename T, int Rows, int Cols>
class Table
{
private:
T m_cells[Rows][Cols]{};
public:
T& at(int row, int col) { return m_cells[row][col]; }
const T& at(int row, int col) const { return m_cells[row][col]; }
};
template <typename T, int Rows, int Cols>
void print(const Table<T, Rows, Cols>& table)
{
for (int r{ 0 }; r < Rows; ++r)
{
for (int c{ 0 }; c < Cols; ++c)
std::cout << table.at(r, c) << ' ';
std::cout << '\n';
}
}
// Overload for char tables - works for any dimensions
template <int Rows, int Cols>
void print(const Table<char, Rows, Cols>& table)
{
for (int r{ 0 }; r < Rows; ++r)
{
for (int c{ 0 }; c < Cols; ++c)
std::cout << table.at(r, c);
std::cout << '\n';
}
}
int main()
{
Table<char, 3, 10> text;
const char* line1{ "Hello" };
const char* line2{ "World" };
for (int i{ 0 }; line1[i]; ++i)
text.at(0, i) = line1[i];
for (int i{ 0 }; line2[i]; ++i)
text.at(1, i) = line2[i];
print(text); // Uses char overload - no spaces
std::cout << '\n';
Table<int, 2, 4> numbers;
numbers.at(0, 0) = 10;
numbers.at(0, 1) = 20;
numbers.at(1, 0) = 30;
numbers.at(1, 1) = 40;
print(numbers); // Uses generic template - with spaces
return 0;
}
Output:
Hello
World
10 20 0 0
30 40 0 0
The char-specific version:
template <int Rows, int Cols>
void print(const Table<char, Rows, Cols>& table)
- Still has template parameters (
RowsandCols) - Fixes
charfor the type parameter - Works for char tables of any size
Partial specialization is for classes only
An important restriction: you can only partially specialize class templates, not standalone function templates. The function example above works because we are overloading (not specializing) print with a different template signature.
For true partial specialization, use class templates:
#include <iostream>
// Primary template
template <typename T, int Rows, int Cols>
class Board
{
private:
T m_cells[Rows][Cols]{};
public:
void set(int r, int c, const T& value) { m_cells[r][c] = value; }
const T& get(int r, int c) const { return m_cells[r][c]; }
void show() const;
};
template <typename T, int Rows, int Cols>
void Board<T, Rows, Cols>::show() const
{
for (int r{ 0 }; r < Rows; ++r)
{
for (int c{ 0 }; c < Cols; ++c)
std::cout << m_cells[r][c] << ' ';
std::cout << '\n';
}
}
// Partial specialization for bool type
template <int Rows, int Cols>
class Board<bool, Rows, Cols>
{
private:
bool m_cells[Rows][Cols]{};
public:
void set(int r, int c, bool value) { m_cells[r][c] = value; }
bool get(int r, int c) const { return m_cells[r][c]; }
void show() const;
};
template <int Rows, int Cols>
void Board<bool, Rows, Cols>::show() const
{
for (int r{ 0 }; r < Rows; ++r)
{
for (int c{ 0 }; c < Cols; ++c)
std::cout << (m_cells[r][c] ? 'X' : '.') << ' ';
std::cout << '\n';
}
}
int main()
{
Board<int, 2, 3> numBoard;
numBoard.set(0, 0, 1);
numBoard.set(0, 1, 2);
numBoard.set(1, 2, 3);
numBoard.show();
std::cout << '\n';
Board<bool, 3, 3> gameBoard;
gameBoard.set(0, 0, true);
gameBoard.set(1, 1, true);
gameBoard.set(2, 2, true);
gameBoard.show();
return 0;
}
Output:
1 2 0
0 0 3
X . .
. X .
. . X
Member functions cannot be partially specialized
You cannot directly partially specialize a member function. Consider this attempt:
// This doesn't compile
template <int Rows, int Cols>
void Board<double, Rows, Cols>::show() const
{
std::cout << std::fixed << std::setprecision(2);
// ...
}
This fails because show() is a member of Board. To customize it for double, you must specialize the entire class:
template <int Rows, int Cols>
class Board<double, Rows, Cols>
{
private:
double m_cells[Rows][Cols]{};
public:
void set(int r, int c, double value) { m_cells[r][c] = value; }
double get(int r, int c) const { return m_cells[r][c]; }
void show() const;
};
template <int Rows, int Cols>
void Board<double, Rows, Cols>::show() const
{
std::cout << std::fixed << std::setprecision(2);
for (int r{ 0 }; r < Rows; ++r)
{
for (int c{ 0 }; c < Cols; ++c)
std::cout << m_cells[r][c] << ' ';
std::cout << '\n';
}
}
This duplicates the entire class. To reduce duplication, use inheritance with a common base:
#include <iostream>
#include <iomanip>
template <typename T, int Rows, int Cols>
class BoardBase
{
protected:
T m_cells[Rows][Cols]{};
public:
void set(int r, int c, const T& value) { m_cells[r][c] = value; }
const T& get(int r, int c) const { return m_cells[r][c]; }
void show() const
{
for (int r{ 0 }; r < Rows; ++r)
{
for (int c{ 0 }; c < Cols; ++c)
std::cout << m_cells[r][c] << ' ';
std::cout << '\n';
}
}
};
template <typename T, int Rows, int Cols>
class Board : public BoardBase<T, Rows, Cols>
{
};
template <int Rows, int Cols>
class Board<double, Rows, Cols> : public BoardBase<double, Rows, Cols>
{
public:
void show() const
{
std::cout << std::fixed << std::setprecision(2);
for (int r{ 0 }; r < Rows; ++r)
{
for (int c{ 0 }; c < Cols; ++c)
std::cout << this->m_cells[r][c] << ' ';
std::cout << '\n';
}
}
};
int main()
{
Board<int, 2, 2> intBoard;
intBoard.set(0, 0, 5);
intBoard.set(1, 1, 7);
intBoard.show();
std::cout << '\n';
Board<double, 2, 2> doubleBoard;
doubleBoard.set(0, 0, 3.14159);
doubleBoard.set(1, 1, 2.71828);
doubleBoard.show();
return 0;
}
Output:
5 0
0 7
3.14 0.00
0.00 2.72
Note: The this-> prefix is required when accessing base class members in a template-dependent context.
Practical use cases
Partial specialization is useful for:
- Type categories: Specializing for all pointer types, all integral types, etc.
- Dimension handling: Different implementations for 1D vs 2D containers
- Algorithm optimization: Different strategies based on type properties
- Storage adaptation: Specialized memory layouts for specific types
Example: Specializing for all pointer types
Create a class template that handles pointers differently:
#include <iostream>
template <typename T>
class Handler
{
public:
static void process(T value)
{
std::cout << "Value: " << value << '\n';
}
};
template <typename T>
class Handler<T*>
{
public:
static void process(T* ptr)
{
if (ptr)
std::cout << "Pointer to: " << *ptr << '\n';
else
std::cout << "Null pointer\n";
}
};
int main()
{
Handler<int>::process(42);
int x{ 100 };
Handler<int*>::process(&x);
Handler<int*>::process(nullptr);
return 0;
}
Output:
Value: 42
Pointer to: 100
Null pointer
The Handler<T*> specialization handles all pointer types, not just int*. This pattern works for double*, char*, or any other pointer type.
Summary
Partial template specialization allows you to:
- Specialize some template parameters while leaving others flexible
- Create type-category specializations (e.g., all pointer types)
- Avoid writing separate full specializations for every combination
Key limitations:
- Only class templates can be partially specialized, not function templates
- Member functions cannot be partially specialized directly
- Use inheritance with a common base to share code between specializations
Partial specialization is an advanced feature best reserved for situations where full specialization or function overloading proves inadequate.
Partially Specialized Templates - Quiz
Test your understanding of the lesson.
Practice Exercises
Matrix with Type-Specific Display
Create a `Matrix` class template with type and dimension parameters. Partially specialize it for char type to display characters without spaces between them.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!