From Java to C++ – Templates
Generic programming is everyday bread for possibly every Java programmer out there. Only the oldest Java programmers out there still remember the times, when we’re casting things all over, and lists were instantiated without explicitly telling the compiler what type of objects they hold. There should be no surprise that the same functionality exists in Java’s older cousin – C++.
DISCLAIMER! This article was written by me, but later, it was reviewed by an experienced C++ programmer/trainer. The parts where something was changed (or a comment was added), are written in blue. The conclusion is, that this post is quite trustworthy now 😉
DISCLAIMER 2! I didn’t want to go with full-blown history lesson here. I’m using the latest C++20 standard and all the examples are compiling and working in it. I may indicate once in a while when specific functionality was introduced.
Let’s go
Generic programming in the C++ is strictly related to the concept of a template. Although, in the Java world we’re used to the idea of generics, templates in C++ are a little different, and in my opinion, harder to grasp. In this article, I will try my best, to present generic programming in C++ in a way, that every Java programmer should easily understand. Let’s start with a definition of template, given by Stroustrup himself.
Basically, a template is a mechanism that allows a programmer to use types as parameters for a class or a function. The compiler then generates a specific class or function when we later provide specific types as arguments.
We can select three separate types of templates:
- function templates
- class templates
- alias templates
- variable templates (since C++14)
Additional typology comes with the template parameters (evaluated during compile time), in other words, what can be passed as a parameter to the template keyword:
- param types – the most common ones, eg.
template<typename|class T>
- none-type parameters – usually already provided value, eg. number 6. Since C++20 it’s also possible to use floating-point numbers and C-style strings (arrays), although with some limitations. However, a lot of other types can be used here, such as enums, pointers of all kinds or std::nullptr_t
- template-template parametersstrong> – when we inject a template into another template
Here we must remember, that the T value, has its own scope, limited to the template only!
Function templates
This type of template should be very familiar to every Java programmer, as they look and work in the same way as generics in Java. We pass some generic class/type to the function, without any additional assumptions.
#include <iostream> using namespace std; template <typename T> // T is usually used to indicate first param, U to indicate second one T myMax(T x, T y) { return (x > y) ? x: y; } // Based on the calls to this function in the main() method // the template will be replaced with three separate implementations of // the above method using ints, doubles and chars as input params. int main() { short myShort = 2; cout << myMax<int>(3, 7) << endl; // Call myMax for int cout << myMax<int>(myShort, 7) << endl; // ERROR - this won't compile as the params are not of the same type cout << myMax<double>(3.0, 7.0) << endl; // call myMax for double cout << myMax<char>('g', 'e') << endl; // call myMax for char return 0; }
First thing to start with is – above template is just a recipe for creating concrete function definitions. The process of that creation is called template instantiation.
Second thing to discuss is whether we should use typename or class in the template definition. Well, as far as I’ve read, it does not matter that much (especially in C++20). Before the usages were somehow limited when template templates were involved. More on that can be found here.
Third important concept is – there’s no implicit conversion, when we’re using templates! That’s why in the above example the line using short variable failed miserably. In our case template expects both arguments to be the same type, and that’s it. No exceptions!
To overcome the above limitation is to use explicit template argument specification (yeah, I know, long name). It is an actual hint to the compiler what will be the type of the used variable. Let’s take a look at this example.
template<class T1, class T2> void PrintNumbers(const T1&amp; t1Data, const T2&amp; t2Data) {} // This PrintNumbers(10, 100); // int, int PrintNumbers(14, 14.5); // int, double PrintNumbers(59.66, 150); // double, int // Can be changed to use this - and therefore limiting the generated specialized versions to only double one PrintNumbers<double, double>(10, 100); // int, int PrintNumbers<double, double>(14, 14.5); // int, double PrintNumbers<double, double>(59.66, 150); // double, int // Generated specialised version void PrintNumbers<double, double>(const double&amp; t1Data, const T2&amp; t2Data) {}
However, it may seem like actually trying to be smarter than the compiler, which not always is a good thing. This technique should also be used, when there’s no way to figure out the type used from the params of the constructor or the methods. Consider this snippet of code:
template<class T> void PrintSize() { cout << "Size of this type:" << sizeof(T); } // Calling it like this will cause compiler error PrintSize(); // But with this - it's working PrintSize<float>();
Going back to the properties of function templates – third thing to mention is that function templates can be only put into global namespace or class scope. There’s no way to restrict their visibility to specified scope.
I’ve mentioned in the comments to the code above, that the compiler will create an actual code for every function template that’s used in the code. However, there’s a possibility to provide our own implementation of these functions if we want to (kind-of like default methods in Java interfaces). Take a look at the following code:
#include <iostream> template <typename T> T myMax(T x, T y) { cout << "Inside template implementation: "; return (x > y) ? x: y; } template<> // <span style="color: rgb(0, 0, 255);" data-mce-style="color: #0000ff;">However, it may be easier to just override and skip using templates</span> short myMax(short x, short y) { cout << "Inside short implementation: "; return (x > y) ? x: y; } int main() { cout << myMax((short)3, (short)7 ) << "\n" << endl; // Call myMax for short cout << myMax(3, 7) << "\n" << endl; // Call myMax for int cout << myMax(3.0, 7.0) << "\n" << endl; // call myMax for double cout << myMax('g', 'e') << "\n" << endl; // call myMax for char return 0; }
Above technique is called specialization, and can be applied to every function template. However, due to weird lookup rules in general it’s better to provide simple, ‘overridden’, version of the function, instead of this concept.
At the end of this section, it’s worth mentioning, that there’s a possibility to indicate to the compiler, a ‘default’ type in the template. I think that the following example will show what I mean.
template<typename T, typename U = char> class A { public: T x; U y; A() { cout << "Constructor Called" << endl; } }; int main() { A<char> a; // This will call A<char, char>; return 0; }
The output will be:
Constructor Called
Class templates
Class templates are working in a very similar way to the function templates, although they are defined at the class level (thank you, Captain Obvious!). As an additional point, there’s a possibility to implement them on a class methods functions outside of the class body (this technique applies also to the definition of ‘normal’ functions). The only thing we have to do while implementing method functions outside the class, is to prefix the function with a class name. Here’s an example:
#include<iostream> using namespace std; template <typename T> class MyClass { public: MyClass(T x, T y) { cout << "My class constructor: " + to_string(x) + " " + to_string(y) + "\n"; } T myMethodOutsideOfClass(); }; template<typename T> T MyClass<T>::myMethodOutsideOfClass() { cout << "Inside short implementation \n"; return 0; } int main() { MyClass<int> myClass(1,2); myClass.myMethodOutsideOfClass(); return 0; }
HINT! As a Java guy I was wondering – what’s the reason to define function outside the class? It’s stupid and puts the class logic in two different places. Well, you are right, it is. However, coming from Java we’re used to the fact, that out JARs are packed with both interfaces and implementations. In C++ (and in C), there’s this whole concept of dynamic/static libraries, and a possibility to separate interface of the class using header files, and use only them during work/writing code. If we put the whole logic inside a class (while it serves also as an interface), with every change in the implementation, we got to recompile the code. With clear separation of class declaration from its definition – we don’t have to. Check out this SO question for more.
There’s one catch in the above – the implementation of the template must still be available to the compiler at the time of header processing! That’s why in a multi-file projects, if we want to expose a header with class declaration, all the template-using functions must also be put in the same file!
When it comes to class templates, we don’t need to operate only on the class level. There’s also a possibility to introduce another templating solution which it called method template (pay attention to the word method, not function). I think that the code will explain it best.
#include<iostream> using namespace std; template <typename T> class MyClass { public: MyClass(T x, T y) { cout << "My class constructor: " << x << " " << y) << "\n"; } template<typename U> void myMethodOfClass(const U&amp; arg) { cout << "Passed arg: " << arg << "\n"; } }; int main() { MyClass<int> myClass(1,2); myClass.myMethodOfClass(5); myClass.myMethodOfClass(2.0f); return 0; }
As we’re in the object-oriented language (more or less), we have to talk a little about inheritance too. In general, there’s no problem with inheriting class templates. The only limitation here, is that we have to explicitly tell the compiler, when we want to use base-class functionality. As usual, here’s the code:
#include<iostream> using namespace std; template<typename T> class Foo { public: void Func() {} }; template<typename T> class Bar : public Foo<T> { public: void BarFunc() { // Func(); This causes compilation error this->Func(); Foo<T>::Func(); } }; int main() { Bar<int> b{}; b.BarFunc(); }
Alias templates
We already saw that templates are great tool to reduce code size. There’s another way in which templates can be useful this way – alias templates. As usual, the best way to describe this functionality is with the simple code snippet:
template <typename T, int Line, int Col> class Matrix { .... }; template <typename T, int Line> using Square = Matrix<T, Line, Line>; // That's the alias template <typename T, int Line> using Vector = Matrix<T, Line, 1>; // Code that uses above constructions Matrix<int, 5, 3> ma; Square<double, 4> sq; Vector<char, 5> vec;
The solution is clear and readable. HINT! The idea of aliasing is not only related to the templates! It can be applied to any other type/definition!
Variable/Variadic templates
For Java programmers the concept should be pretty familiar – varargs used in the templates. That’s it. In C++ this vararg is called template parameter pack, when used in the strong>template, and function parameter pack. Here’s the code:
// Args as a type/name is completely arbitrary and can be anything template<typename... Args> // this is for the template void printMyParams(Args... args) // this is for the function
I don’t want to dive into the concept of parameter pack right now. The best resource to get an understanding how it works can be found here. What I want to show is to how can they be used in the templates.
#include<iostream> template <typename T, typename... Args> T sum(T t, Args... args) { // [if constexpr] is evaluated during compile time // sizeof... is a special operator to determine the amount of params passed if constexpr(sizeof...(args) > 0) { return t + sum(args...); } else { // This ELSE must be here - skipping it will cause logical error as we have constexpr here, not simple IF return t; } } int main() { std::cout << sum(1,2,3); }
Executing this program will result in printed 6. In order for our example to work, an implementation of
double sum(T t)
should be provided. We avoid the need for that, by checking the amount of variable params passed to the function. As long as it is more than 0, we recursively call the function. If there’s no additional params to sum, we just return the passed value. Here is the code with a separate function written.
#include<iostream> template<typename T> auto sum(T t) { return t; } template<typename T, typename... Args> auto sum(T t, Args... args) { return t + sum(args...); } int main() { std::cout << sum(1, 2, 3); }
To sum up the presentation of parameter packs, I’ll show something, that is available since C++17 – folding (concept known from the functional languages) – if you’re interested you can read about it on the official page.
#include<iostream> template<typename... Args> auto sum(Args... args) { return (... + args); } int main() { std::cout << sum(1, 2, 3); }
Static template class members
Every function or class can contain static members. The wide question to ask would be – how do they behave when they’re used in the templates? The answer lies in the statement presented in the previous sub-chapter – templates are a recipe for creating specific functions or classes. Therefore, for every calculated combination of the types, a separate instance of function/class will be generated by the compiler. Therefore, every instance will have its own, private static member.
Concepts
In this article I will just mention them. First – because they deserve more descriptive article, than just subchapter here. Second – it’s a new feature, available in C++20. Therefore, it’s not widely used yet. In general – in Java we write something like this:
class MyClass<T extends SomeOtherClass>() { T someOtherClass; }
At the compilation level we check, whether passed object meets the criteria of extends SomeOtherClass. It’s a very powerful tool, and that’s why C++ introduced it too. However, as I’ve said, I will write a separate article later, and link it here.
SOURCES:
- CppCon 2020 ‘Back to Basis – Templates part 1’ talk
- CppCon 2020 ‘Back to Basis – Templates part 2’ talk
- Back to Basics: Function and Class Templates – Dan Saks – CppCon 2019
- Bjarne Stroustrup ‘Programming Principles and Practice Using C++ 2ed’ book
- Josh Lospinoso ‘C++ Crash Course’ book
- Templates in C++ vs generics in Java article
- Typename vs class
- Template params and alias templates
- Guide to C++ templates
- Constexpr if explained
- Parameter pack
One Comment
Leave a Reply
You must be logged in to post a comment.
Tour of C++ review – Bare.Metal.Dev
[…] templates. I know it’s not that easy topic. However, looking at it from the perspective of written article about them, I can see clearly how badly was this chapter written. Unfortunately, later it’s not getting […]