DEV Community

Michał Piotrowski
Michał Piotrowski

Posted on • Originally published at baremetaldev.com

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! Obviously I'm not an experienced C++ programmer, therefore there's always a possibility, that my knowledge is not enough. I've put a lot of effort, to write this article and make it as good as possible. However, if you think that some concepts or explanations are wrong, please, don't hesitate to point them out!

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 asarguments.

We can select three separate types of templates:

  • function templates
  • class 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 as enums, pointers of all kinds or std::nullptr_t

  • template-template parameters - 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;
}
Enter fullscreen mode Exit fullscreen mode

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& t1Data, const T2& 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& t1Data, const T2& t2Data)
{}
Enter fullscreen mode Exit fullscreen mode

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>();
Enter fullscreen mode Exit fullscreen mode

Going back to the properties of function templates - third thing to mention is that function templates can be only put into global namespace. 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>
using namespace std;


template <typename T>
T myMax(T x, T y)
{
    cout << "Inside template implementation: ";
    return (x > y) ? x: y;
}

template<>
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;
}
Enter fullscreen mode Exit fullscreen mode

Above technique is called specialization, and can be applied to every function template.

At the end of this section, it's worth mentioning, that there's a possibility to indicate to the compiler, which generated method should be called. I think that the following example will show what I mean.

template<class T, class 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;
}
Enter fullscreen mode Exit fullscreen mode

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 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 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;
}
Enter fullscreen mode Exit fullscreen mode

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: " + to_string(x) + " " + to_string(y) + "\n";
    }

    template<typename U>
    void myMethodOfClass(const U& arg)
    {
        cout << "Passed arg: " + to_string(arg) + "\n";
    }
};

int main()
{
    MyClass<int> myClass(1,2);
    myClass.myMethodOfClass(5);
    myClass.myMethodOfClass(2.0f);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 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
Enter fullscreen mode Exit fullscreen mode

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...);
    }

    return t;
}

int main()
{
    std::cout << sum(1,2,3);
}
Enter fullscreen mode Exit fullscreen mode

Executing this program will result in printed 6. In order for our example to work, an implementation of

cppdouble 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>
T sum(T t)
{
    return t;
}

template<typename T, typename... Args>
T sum(T t, Args... args) {
    return t + sum(args...);
}

int main()
{
    std::cout << sum(1,2,3);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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:

Top comments (2)

Collapse
 
aminmansuri profile image
hidden_dude

One of the things I liked of Java (when it was still new) after 10 years of C++ was it's simplicity and cleanliness.

It's funny to read an article about going back to C++.

Don't get me wrong, I love to program in C++ or any other language, but for application programming Java was a great leap of productivity when compared to C++. Finally we had modern things like garbage collection, write once & run anywhere, memory protection, cool runtime introspection and other features we had only seen in languages like Smalltalk.

Also, we no longer had to tangle with C++ templates.

;)

Collapse
 
chlebik profile image
Michał Piotrowski

Thank You for the comment. I'm learning C++ in order to get into JVM internals programming, hence this post. I don't plan to leave Java for C++ in general ;) More on that on my actual blog - baremetaldev.com/2021/10/04/one-ye...