In previous lessons, you’ve learned how to use template type parameters to create functions and classes that are type independent. However, template type parameters are not the only type of template parameters available. Template classes (not template functions) can make use of another kind of template parameter known as an expression parameter.
Expression parameters
A template expression parameter is a parameter that does not substitute for a type, but is instead replaced by a value. An expression parameter can be any of the following:
- A value that has an integral type or enumeration
- A pointer or reference to an object
- A pointer or reference to a function
- A pointer or reference to a class member function
In the following example, we create a buffer class that uses both a type parameter and an expression parameter. The type parameter controls the data type of the buffer array, and the expression parameter controls how large the buffer array is.
template <typename T, int nSize> // nSize is the expression parameter
class Buffer
{
private:
// The expression parameter controls the side of the array
T m_atBuffer[nSize];
public:
T* GetBuffer() { return m_atBuffer; }
T& operator[](int nIndex)
{
return m_atBuffer[nIndex];
}
};
int main()
{
// declare an integer buffer with room for 12 chars
Buffer<int, 12> cIntBuffer;
// Fill it up in order, then print it backwards
for (int nCount=0; nCount < 12; nCount++)
cIntBuffer[nCount] = nCount;
for (int nCount=11; nCount >= 0; nCount–)
std::cout << cIntBuffer[nCount] << " ";
std::cout << std::endl;
// declare a char buffer with room for 31 chars
Buffer<char, 31> cCharBuffer;
// strcpy a string into the buffer and print it
strcpy(cCharBuffer.GetBuffer(), "Hello there!");
std::cout << cCharBuffer.GetBuffer() << std::endl;
return 0;
}
This code produces the following:
11 10 9 8 7 6 5 4 3 2 1 0 Hello there!
One noteworthy thing about the above example is that we do not have to dynamically allocate the m_atBuffer member array! This is because for any given instance of the Buffer class, nSize is actually constant. For example, if you instantiate a Buffer
Template specialization
When instantiating a template class for a given type, the compiler stencils out a copy of each templated member function, and replaces the template type parameters with the actual types used in the variable declaration. This means a particular member function will have the same implementation details for each instanced type. While most of the time, this is exactly what you want, occasionally there are cases where it is useful to implement a templated member function slightly different for a specific data type. Template specialization lets you accomplish exactly this.
Let’s take a look at a very simple example:
using namespace std;
template <typename T>
class Storage
{
private:
T m_tValue;
public:
Storage(T tValue)
{
m_tValue = tValue;
}
~Storage()
{
}
void Print()
{
std::cout << m_tValue << std::endl;;
}
};
The above code will work fine for many data types:
int main()
{
// Define some storage units
Storage<int> nValue(5);
Storage<double> dValue(6.7);
// Print out some values
nValue.Print();
dValue.Print();
}
This prints:
5 6.7
Now, let’s say we want double values to output in scientific notation. To do so, we will need to use template specialization to create a specialized version of the Print() function for doubles. This is extremely simple: simply define the specialized function outside of the class definition, replacing the template type with the specific type you wish to redefine the function for. Here is our specialized Print() function for doubles:
void Storage<double>::Print()
{
std::cout << std::scientific << m_tValue << std::endl;
}
When the compiler goes to instantiate Storage<double>::Print(), it will see we’ve already defined one, and it will use the one we’ve defined instead of stenciling out a version from the generic templated member function.
As a result, when we rerun the above program, it will print:
5 6.700000e+000
Now let’s take a look at another example where template specialization can be useful. Consider what happens if we try to use our templated Storage class with datatype char*:
int main()
{
using namespace std;
// Dynamically allocate a temporary string
char *strString = new char[40];
// Ask user for their name
cout << "Enter your name: ";
cin >> strString;
// Store the name
Storage<char*> strValue(strString);
// Delete the temporary string
delete strString;
// Print out our value
strValue.Print(); // This will print garbage
}
As it turns out, instead of printing the name the user input, strValue.Print() prints garbage! What’s going on here?
When Storage is instantiated for type char*, the constructor for Storage<char*> looks like this:
Storage<char*>::Storage(char* tValue)
{
m_tValue = tValue;
}
In other words, this just does a pointer assignment! As a result, m_tValue ends up pointing at the same memory location as strString. When we delete strString in main(), we end up deleting the value that m_tValue was pointing at! And thus, we get garbage when trying to print that value.
Fortunately, we can fix this problem using template specialization. Instead of doing a pointer copy, we’d really like our constructor to make a copy of the input string. So let’s write a specialized constructor for datatype char* that does exactly that:
Storage<char*>::Storage(char* tValue)
{
// Allocate memory to hold the tValue string
m_tValue = new char[strlen(tValue)+1];
// Copy the actual tValue string into the m_tValue memory we just allocated
strcpy(m_tValue, tValue);
}
Now when we allocate a variable of type Storage<char*>, this constructor will get used instead of the default one. As a result, m_tValue will receive its own copy of strString. Consequently, when we delete strString, m_tValue will be unaffected.
However, this class now has a memory leak for type char*, because m_tValue will not be deleted when the Storage variable goes out of scope. As you might have guessed, this can also be solved by specializing the Storage<char*> destructor:
Storage<char*>::~Storage()
{
delete[] m_tValue;
}
Now when variables of type ~Storage<char*> go out of scope, the memory allocated in the specialized constructor will be deleted in the specialized destructor.
14.5 — Class template specialization
|
Index
|
14.3 — Template Classes
|
14.5 — Class template specialization
Index
14.3 — Template Classes
Alex,
I just finished your awesome tutorial, I felt my understanding to C++ really improved a lot.
Would you please recommand a couple of C++ books because I really want to keep all the learnings up and may dig more into it?
I am a VBA programmer for a while and want to be involved in C++ programming work. I think the only way to improve C++ programming skill is to do the real world programming. I am not a CS major. Is there any tests that can tell employers my C++ level and help me land with some entry level C++ programmer?
Thanks a lot,
Sean,
I’m not really sure what to recommend to you bookwise, as you’ll find a lot of books are rather redundant with this tutorial. Instead, I would highly advise assigning yourself a project that will test your skills and make you put the concepts you’ve learned into play. You’ll learn more from doing that than anything at this point.
I do not know of any standardized C++ proficiency tests. Many employers have their own versions and will have you take them when you apply (if they like your resume enough).
To specialize a template there still has to be the template keyword before the specialized function, followed by an empty parameter list; i.e. this won’t compile:
1. void Storage<double>::Print()
2. {
3. std::cout << scientific << m_tValue << std::endl;
4. }
This is how it has to be:
1. template
2. void Storage::Print()
3. {
4. std::cout << scientific << m_tValue << std::endl;
5. }
No, sorry, this
1. template
2. void Storage::Print()
3. {
4. std::cout << scientific << m_tValue << std::endl;
5. }
is not how it has to be - the html paser killed the empty parameter list, this should be better:
1. template <>
2. void Storage::Print()
3. {
4. std::cout << scientific << m_tValue << std::endl;
5. }
That doesn’t make any sense to me. If you have an empty parameter list, how would the compiler know which template type to specialize the function for?
Hi Ben,Alex
VS2005 does accept template <> but it seems to be optional. I think it is just a way to tell the compiler that you are intending to specialize a template function - so using template <> before any non-template overriding function generates an error.
void Storage<double>::Print() { std::cout << scientific << m_tValue << std::endl; }I think this should be std::scientific in order to work.
[ I believe you are correct. Thanks! -Alex ]
Hi Alex,
great tutorial. Thanks a lot.
First example in line 11 should read
instead of
[ Yes, thank you. -Alex ]