Sometimes a generic template does the wrong thing for a single specific type. It's necessary for the compiler to have a function or a class for this specific type, yet the body of the primary template is inappropriate for this case. A full template specialization gives you a totally different definition to be used when the proper type is encountered.
A classic example of this is a simple function to print things. The primary template will work for many types:
#include <iostream>
// Make the following definition depend on template arguments
template <typename T>
void show(T value)
{
std::cout << value << '\n';
}
int main()
{
show(42);
show(3.14);
return 0;
}
The template parameter T represents the type that is passed as an argument. In main, the first call requests an instantiated version of show that takes an int parameter. The compiler generates the version and inserts it.
The second call instantiates a version that takes a double.
The compiler generates the function and inserts it also. Both calls are processed as intended.
When using the same template for printing a boolean, the primary template is still selected, though for reasons that are not always obvious:
#include <iostream>
template <typename T>
void show(T value)
{
std::cout << value << '\n';
}
int main()
{
show(true);
return 0;
}
When you call show(true), the compiler instantiates the primary template with T = bool. It then inserts the given boolean into the stream. The default streaming behavior for booleans causes 1 or 0 to be displayed. The inserted value is technically correct, but it doesn't display in the way expected.
A different, specialized definition of show can be provided only for bool:
#include <iostream>
template <typename T>
void show(T value)
{
std::cout << value << '\n';
}
// Provide an implementation for this exact specialization
template <>
void show<bool>(bool value)
{
std::cout << (value ? "true" : "false") << '\n';
}
int main()
{
show(42);
show(true);
return 0;
}
The second definition of show begins with the token template <> which tells the compiler that what follows is a full specialization. After template <>, we have the name of the template, but this time in its full form: show<bool>, followed by the specification of the parameter that is now guaranteed to be a bool. The body of this specialized function doesn't work with insertion stream operators; instead, it checks the value and prints either "true" or "false".
When the program gets to the call show(true), the compiler looks for a specialization matching this parameter, and finds it. Hence, the primary template is not even taken into account for the function call show(true), and the personalized version is used, printing "true". The previous call to show(42), for instance, uses the primary template because there is no specialization for int.
This method is just as useful for class templates as for function templates. A class template like Holder that stores a single element can have specialized versions.
The primary template defines a class that can store an element of any type T. A specialization is possible when, for instance, we want the Holder to store a character but provide an additional member function, named print, that will actually print it. To define this specialization, the syntax is exactly the same as for function templates, starting with template <>, then the name of the specialized template with its type argument: Holder<char>, and finally the body of the specialized class:
#include <iostream>
template <typename T>
// Define the members that belong to this type
struct Holder
{
T data{};
};
template <>
struct Holder<char>
{
char data{};
void print()
{
std::cout << data << '\n';
}
};
int main()
{
Holder<int> h1{};
h1.data = 99;
std::cout << h1.data << '\n';
Holder<char> h2{};
h2.data = 'Z';
h2.print();
return 0;
}
When you declare h1 of type Holder<int>, it contains a single data member: data. When you declare h2 of type Holder<char>, it contains both the data member data and the method print. The two types are then distinct: an instance of Holder<int> cannot be treated as an instance of Holder<char>.
Exercise: Template SpecializationStarter code, hints and solutionOpenClose
Practice: Template Specialization
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Instantiate Holder<double>, assign 2.5 to its data, and print that member directly. Explain why the char specialization's print function is not available on this type.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
template <typename T>
// Define the members that belong to this type
struct Holder
{
T data{};
};
template <>
struct Holder<char>
{
char data{};
void print()
{
std::cout << data << '\n';
}
};
int main()
{
Holder<int> h1{};
h1.data = 99;
std::cout << h1.data << '\n';
Holder<char> h2{};
h2.data = 'Z';
h2.print();
return 0;
}Hints
Holder<char> has a full specialization that includes print(); the primary Holder<T> definition contains only data. The specialization is a separate class definition, not an extra method added to every Holder.
Instantiate Holder<double> and use its data member. Calling its print() would fail because only the char specialization defines that member.
Worked solution
Holder<double> uses the primary template, not the char specialization. Its data is set to 2.5 and printed directly; it has no print member. The original char specialization retains its separate print method.
#include <iostream>
template <typename T>
// Define the members that belong to this type
struct Holder
{
T data{};
};
template <>
struct Holder<char>
{
char data{};
void print()
{
std::cout << data << '\n';
}
};
int main()
{
Holder<int> h1{};
h1.data = 99;
std::cout << h1.data << '\n';
Holder<char> h2{};
h2.data = 'Z';
h2.print();
Holder<double> extra{};
extra.data = 2.5;
std::cout << extra.data << '\n';
return 0;
}
Example output
99
Z
2.5
0 comments
No comments yet. Be the first.