Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Specialized Template Implementations
Create entirely different class implementations for specific types.
In the previous lesson, you learned how to specialize function templates for specific types. Class template specialization extends this concept to entire classes, allowing completely different implementations for specific types while presenting a consistent interface.
Why specialize class templates?
Consider a fixed-size buffer that stores exactly 8 values:
#include <iostream>
template <typename T>
class Buffer8
{
private:
T m_data[8]{};
public:
void set(int index, const T& value)
{
m_data[index] = value;
}
const T& get(int index) const
{
return m_data[index];
}
};
int main()
{
Buffer8<int> scores;
for (int i{ 0 }; i < 8; ++i)
scores.set(i, (i + 1) * 10);
std::cout << "Scores: ";
for (int i{ 0 }; i < 8; ++i)
std::cout << scores.get(i) << ' ';
std::cout << '\n';
Buffer8<bool> switches;
for (int i{ 0 }; i < 8; ++i)
switches.set(i, (i % 2) == 0);
std::cout << std::boolalpha << "Switches: ";
for (int i{ 0 }; i < 8; ++i)
std::cout << switches.get(i) << ' ';
std::cout << '\n';
return 0;
}
Output:
Scores: 10 20 30 40 50 60 70 80
Switches: true false true false true false true false
This works but wastes memory for bool. Each bool occupies one byte (8 bits), even though it only needs 1 bit. Buffer8<bool> uses 8 bytes when 1 byte would suffice - 8 bits can store 8 boolean values.
Class template specialization syntax
Specialization lets you provide an optimized implementation for specific types:
#include <iostream>
#include <cstdint>
template <typename T>
class Buffer8
{
private:
T m_data[8]{};
public:
void set(int index, const T& value)
{
m_data[index] = value;
}
const T& get(int index) const
{
return m_data[index];
}
};
template <>
class Buffer8<bool>
{
private:
std::uint8_t m_bits{};
public:
void set(int index, bool value)
{
std::uint8_t mask{ static_cast<std::uint8_t>(1 << index) };
if (value)
m_bits |= mask;
else
m_bits &= ~mask;
}
bool get(int index) const
{
std::uint8_t mask{ static_cast<std::uint8_t>(1 << index) };
return (m_bits & mask) != 0;
}
};
int main()
{
Buffer8<int> scores;
for (int i{ 0 }; i < 8; ++i)
scores.set(i, (i + 1) * 10);
std::cout << "Scores: ";
for (int i{ 0 }; i < 8; ++i)
std::cout << scores.get(i) << ' ';
std::cout << '\n';
Buffer8<bool> switches; // Uses specialized version
for (int i{ 0 }; i < 8; ++i)
switches.set(i, (i % 2) == 0);
std::cout << std::boolalpha << "Switches: ";
for (int i{ 0 }; i < 8; ++i)
std::cout << switches.get(i) << ' ';
std::cout << '\n';
return 0;
}
The specialized version uses only 1 byte (std::uint8_t) instead of 8 bytes, achieving 8x compression.
Key points:
template <>indicates a specialization with no remaining template parametersclass Buffer8<bool>specifies which class and type we are specializing- The specialization is a completely independent class
- The compiler automatically selects the specialization when
Tisbool
Specializations are independent classes
Specialized classes have no connection to the primary template beyond sharing a name. You can:
- Change member variables entirely
- Add or remove member functions
- Modify function signatures
- Alter access specifiers
- Use different base classes
The only requirement: the primary template declaration must precede the specialization.
Example with extended functionality:
#include <iostream>
#include <string>
template <typename T>
class Cache
{
private:
T m_value{};
public:
Cache(T value) : m_value{ value } {}
void show() const
{
std::cout << m_value << '\n';
}
};
template <>
class Cache<std::string>
{
private:
std::string m_value;
std::string m_tag;
public:
Cache(const std::string& value, const std::string& tag = "")
: m_value{ value }, m_tag{ tag }
{
}
void show() const
{
if (m_tag.empty())
std::cout << m_value << '\n';
else
std::cout << "[" << m_tag << "] " << m_value << '\n';
}
void setTag(const std::string& tag)
{
m_tag = tag;
}
};
int main()
{
Cache<int> count{ 42 };
count.show();
Cache<std::string> message{ "Hello", "greeting" };
message.show();
message.setTag("farewell");
message.show();
return 0;
}
Output:
42
[greeting] Hello
[farewell] Hello
The std::string specialization adds m_tag and setTag() that do not exist in the primary template.
Specializing individual member functions
Instead of specializing the entire class, specialize only the member functions that need different behavior:
#include <iostream>
#include <iomanip>
template <typename T>
class Slot
{
private:
T m_value{};
public:
Slot(T value) : m_value{ value } {}
void display() const;
};
template <typename T>
void Slot<T>::display() const
{
std::cout << m_value << '\n';
}
template <>
void Slot<float>::display() const
{
std::cout << std::fixed << std::setprecision(2) << m_value << '\n';
}
template <>
void Slot<double>::display() const
{
std::cout << std::scientific << std::setprecision(4) << m_value << '\n';
}
int main()
{
Slot<int> count{ 42 };
count.display();
Slot<float> ratio{ 3.14159f };
ratio.display();
Slot<double> measurement{ 2.71828 };
measurement.display();
return 0;
}
Output:
42
3.14
2.7183e+00
This avoids duplicating the entire class. The compiler:
- Instantiates
Slot<float>andSlot<double>from the primary template - Replaces their
display()methods with the specialized versions
Member function specializations in header files must be marked inline.
Avoiding code duplication
Specializing an entire class can lead to redundancy:
// Redundant - duplicates everything for one change
template <typename T>
class Record
{
private:
T m_data{};
public:
Record(T data) : m_data{ data } {}
T getData() const { return m_data; }
void setData(T data) { m_data = data; }
void print() const
{
std::cout << m_data << '\n';
}
};
template <>
class Record<double>
{
private:
double m_data{};
public:
Record(double data) : m_data{ data } {}
double getData() const { return m_data; }
void setData(double data) { m_data = data; }
void print() const
{
std::cout << std::fixed << std::setprecision(6) << m_data << '\n';
}
};
Better approach - specialize only print():
template <typename T>
class Record
{
private:
T m_data{};
public:
Record(T data) : m_data{ data } {}
T getData() const { return m_data; }
void setData(T data) { m_data = data; }
void print() const
{
std::cout << m_data << '\n';
}
};
template <>
void Record<double>::print() const
{
std::cout << std::fixed << std::setprecision(6) << m_data << '\n';
}
Much cleaner and more maintainable.
Where to define specializations
For specializations to work correctly, the compiler must see both:
- The primary template definition
- The specialized class definition
Place both in the same header file, with the specialization below the primary template:
// Buffer.h
#pragma once
// Primary template
template <typename T>
class Buffer
{
// ... primary implementation
};
// Specialization immediately after
template <>
class Buffer<bool>
{
// ... specialized implementation
};
If a specialization is only needed in one translation unit, define it in that .cpp file. Other files will use the primary template.
Avoid putting specializations in separate headers. If you forget to include the specialization header, code silently uses the primary template, causing subtle bugs.
Practical example: Allocator optimization
Different allocation strategies for different types:
#include <iostream>
template <typename T>
class Allocator
{
public:
T* create()
{
return new T{};
}
void destroy(T* ptr)
{
delete ptr;
}
};
template <>
class Allocator<char>
{
private:
static constexpr int POOL_SIZE{ 256 };
char m_pool[POOL_SIZE];
int m_next{ 0 };
public:
char* create()
{
if (m_next >= POOL_SIZE)
return nullptr;
return &m_pool[m_next++];
}
void destroy(char*)
{
// Pool allocator doesn't free individual chars
}
void reset()
{
m_next = 0;
}
};
int main()
{
Allocator<int> intAlloc;
int* num{ intAlloc.create() };
*num = 42;
std::cout << "Number: " << *num << '\n';
intAlloc.destroy(num);
Allocator<char> charAlloc;
for (int i{ 0 }; i < 10; ++i)
{
char* ch{ charAlloc.create() };
if (ch)
*ch = 'A' + i;
}
return 0;
}
The char specialization uses a pre-allocated memory pool for efficiency, while the primary template uses standard dynamic allocation.
Summary
Class template specialization enables:
- Optimized implementations for specific types (e.g., bit-packing for bool)
- Type-specific functionality with a common interface
- Member function specialization to customize individual methods
- Completely different implementations that are transparent to users
When specializing:
- Prefer member function specialization over full class specialization to reduce duplication
- Place specializations in the same header as the primary template
- Remember that specializations are independent classes that happen to share a name
Class template specialization is a powerful tool for creating type-aware, optimized generic code while maintaining a consistent interface.
Specialized Template Implementations - Quiz
Test your understanding of the lesson.
Practice Exercises
Storage Container with Bool Optimization
Create a `Storage` class template that holds a single value. Then specialize it for bool to print 'enabled'/'disabled' instead of the boolean value when displaying.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!