Introduction In previous articles I have already described a lot of things about templates (the full list of articles you can find at the end). I told basic information about templates: their disadvantages, interacting with OOP, typename keyword, but it is not enough! There are still many things to investigate and learn. Today my topics are specification of templates and duck typing technique which forms basis for templates. Template specification As you already know that template class\function provides generating of code for given types. However, what should you do if you need to change behavior of template code for some reasons for specific type? This is for what template specification stands for. It provides instrument to completely change implementation of template for some type or set of types. Let's begin with the following example of template class:
#include <algorithm>
template<typename TDataKeeper, typename TDataType>
class SpecificationTemplate
{
public:
SpecificationTemplate(const TDataKeeper &input)
{
this->size = sizeof(someData) / sizeof(TDataType);
std::copy(input, input + size, someData);
}
TDataType* ExtendWithValue(TDataType value)
{
TDataType *returnValue = new TDataType[this->size + 1];
std::copy(someData, someData + size, returnValue);
returnValue[this->size] = value;
return returnValue;
}
private:
TDataKeeper someData;
size_t size;
};
I have declared template class which keeps some data and it must be a container with continuous item - for this I will use static arrays. In constructor of this class you see that input argument will be copied to private field someData. Method ExtendWithValue() allocates new dynamic array in which copies internal data, adds input value to the end of dynamic array and returns it. Yep, it means that there will be returned new array which is bigger than internal array on one item and its last item will be passed value. It is some kind of extending. Let's see it in action:
const size_t beginSize = 3;
const size_t extendedSize = 4;
int arr[beginSize] = {1, 2, 3}; (1.1)
SpecificationTemplate<int[beginSize], int> intArray(arr); (1.2)
std::unique_ptr<int[]> biggerArr(intArray.ExtendWithValue(4)); (1.3)
// It prints: 1, 2, 3, 4
for (size_t i = 0; i < extendedSize; ++i)
{
std::cout << biggerArr[i] << " ";
}
std::cout << std::endl;
So, in line (1.1) there is declared static array with dimension - 3 and items - 1, 2, 3. In line (1.2) there is an instance of our template class which template arguments are: TDataKeeper is int[3] and TDataType is int. This instance holds copy of passed array as argument. Finally in line (1.3) method ExtendWithValue(4) is called which returns new array whith dimension - 4 and its items - 1, 2, 3, 4. Those items are printed to standard output. Okay, after some time we want something similar but with std::string as template argument. Most probably it won't work, but template specification is going to help us! See the following code with specification:
#include <string>
template<> (2.1)
class SpecificationTemplate<std::string, char> (2.2)
{
public:
SpecificationTemplate(const std::string &input) (2.3)
{
this->someString = input;
}
std::string ExtendWithValue(char ch) (2.4)
{
std::string returnValue = this->someString;
returnValue.push_back(ch); (2.5)
return returnValue;
}
private:
std::string someString;
};
Code above declares specification of template class SpecificationTemplate for types std::string and char. Let's observe lines in their order. Line (2.1) contains line with template keyword, but it has empty template argument list! Because in next line (2.2) we manually specify template arguments as TDataKeeper is std::string and TDataType is char. So, in lines (2.3) and (2.4) we can use those types directly which lets us to implement SpecificationTemplate class in a different way then the main template. For example in line (2.5) we use method std::string::push_back() to extend copy of internal string. Thus it works in the following example:
std::string input("some text");
SpecificationTemplate<std::string, char> stringBike(input);
std::string newString = stringBike.ExtendWithValue('.');
// It prints: some text.
std::cout << newString << std::endl;
Without specification of template for std::string this code will cause compile errors, but we have declared specification for such types and compiler can use it here. As you see specification has absolutely different implementation, because instead of auto-generating new code compiler takes our specified SpecificationTemplate<std::string, char>. Of course, specification of templates doesn't need concretization of all template arguments. Header of SpecificationTemplate can look like this:
template<typename TDataKeeper>
class SpecificationTemplate<TDataKeeper, double>
{
...
};
Actually, I have already mentioned this technique when i was talking about std::unique_ptr for single instances and std::unique_ptr<T[]> for dynamic arrays. Now it must be obvious for you that correct freeing of allocated memory by new and new[] is provided by specifying template with correct destructor. Duck typing or can you quack for me? Duck test says: `When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.` So how it looks in programming? Especially in C++? As always I will start with some examples to show: 1. I want to begin from afar with very common practice of command pattern implementation, I think you know it:
class Command
{
public:
virtual void Execute()=0;
};
class AwesomeCommand0 : public Command
{
public:
virtual void Execute()
{
std::cout << "AwesomeCommand0::Execute()" << std::endl;
}
};
class AwesomeCommand1 : public Command
{
public:
virtual void Execute()
{
std::cout << "AwesomeCommand1::Execute()" << std::endl;
}
};
void ExecuteCommand(Command *command)
{
command->Execute();
}
...
AwesomeCommand0 command0;
AwesomeCommand1 command1;
// It prints: AwesomeCommand0::Execute()
ExecuteCommand(&command0);
// It prints: AwesomeCommand1::Execute()
ExecuteCommand(&command1);
As you see I have used interface of abstract class Command to implement two different commands AwesomeCommand0 and AwesomeComman1 for using them through one function which executes them having no idea what command it is exactly, but it knows about their parent class. 2. Now let's make the same, but without inheritance with help of templates. Check it out in the following code:
class AdorableCommand0
{
public:
void Execute()
{
std::cout << "AdorableCommand0::Execute()" << std::endl;
}
};
class AdorableCommand1
{
public:
void Execute()
{
std::cout << "AdorableCommand1::Execute()" << std::endl;
}
};
template<typename TCommand>
void ExecuteTCommand(TCommand *command)
{
command->Execute();
}
...
AdorableCommand0 tcommand0;
AdorableCommand1 tcommand1;
// It prints: AdorableCommand0::Execute()
ExecuteTCommand(&tcommand0);
// It prints: AdorableCommand1::Execute()
ExecuteTCommand(&tcommand1);
// It prints: AwesomeCommand0::Execute()
ExecuteTCommand(&command0);
// It prints: AwesomeCommand1::Execute()
ExecuteTCommand(&command1);
As you see AdorableCommand0 and AdorableCommand1 don't have same parent, BUT at the same time they use in template function ExecuteTCommand(). Furthermore ExecuteTCommand can handle instances of classes AwesomeCommand0 and AwesomeCommand1 at the same time. It is because of duck typing. Templates have such mechanism for generating code: if template argument type has every property which is used in code than it is duck... I am sorry: correct code and can be generated anyway, otherwise it will cause compile errors during building. Because duck typing in C++ has static behavior during compiling process. If you tried to use templates from some library, probably you would face with such compile errors because your implementation wasn't duck and it would go away when you make it quacking. In such way by help of duck typing templates provide style of typing in which instance can have valid semantic without inheritance or explicit interface. On the other hand it causes overhead with looking what is necessary for type to use it in template code. Programmer have to look up for it. Conclusion in this article I continued talking about templates. I have dived deeper with you in a template specification with duck typing. First one provides powerful method to change implementation of original template even for other purpose with specified type or types, but you have to remember that someone else can have no idea about big changes in behavior of template with specified types. Moreover, I have shown duck typing in C++ which is presented by static checking of type properties in template code. Can given type be used or not? It can be used for providing common usage of instances of different type without its inheritance from parent class or having explicit interface. On the other hand it can cause more overhead than normal. In the next article I will talk about calculating at compile time. Sure, it will be interesting. My previous articles from “.CPP files” series are:
- “Do not muddle pointers and references”;
- “Extended talk about references and pointers”;
- “Haters gonna hate or life without pointers before C++11”;
- “Haters gonna hate or life without pointers in C++11 times”;
- “I have seen incomplete type”;
- “Template as double-edged sword: basic knowledge”;
- “Template as double-edged sword: go further with OOP!”.