Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save championswimmer/da30db04850dfbdbbedb37b24a68d005 to your computer and use it in GitHub Desktop.
Save championswimmer/da30db04850dfbdbbedb37b24a68d005 to your computer and use it in GitHub Desktop.
C++ Templates vs Java Generics

Templates in C++ vs Generics in Java

Templates and generics allow us to achieve very similar results using very similar code syntax in C++ and Java respectively. But despite that, the implementation of these two features and their semantics are vastly different under the hood.

Syntax and Usage

Templates

Let us consider first the case of a template called Pair in C++.

template <class T> class Pair {  
private:  
  T left, right;  
public:  
  Pair(T left, T right) {  
  this->left = left;  
  this->right = right;  
  }  
  T getLeft() { return left; }  
  T getRight() { return right; }  
};

And we can then make us of the templates like this -

int main() {  
  
  auto *i = new Pair<int>(10, 20);  
  auto *s = new Pair<char const *>("hello", "world");  
  
  auto *f = new Pair<float>(1,2);  

  std::cout << i->getLeft() << std::endl;  
  std::cout << s->getRight() << std::endl;  
  std::cout << f->getRight() << std::endl;  
  return 0;  
}

Generics

Now let's try to achieve the same using Generics in Java

static class Pair<T> {
    private T left, right;
    public Pair(T left, T right) {
    	this.left = left;
    	this.right = right;
    }
    public T getLeft() {return left;}
    public T getRight() {return right;}
}

Which we would then use so -

public static void main (String[] args)  {
    Pair<Integer> i = new Pair(1,2);
    Pair<String> s = new Pair("hello", "world");
    Pair<Float> f = new Pair(1,2);

    System.out.println(i.getLeft());
    System.out.println(s.getRight());
    System.out.println(f.getRight());

}

Note that up until Java 6 you'd have to write

Pair<Integer> i = new Pair<Integer>(1,2);

But since Java 7, the generic parameter can be inferred from the LHS. So both these lines work

Pair<Integer> i = new Pair<>(1,2);
// or
Pair<Integer> i = new Pair(1,2);

Differences

If you go and search about differences between templates and generics you'll find quite a few articles.

And while it is good to keep in mind things like Java generics do not allow primitives, only classes in mind, those are things you internalise very soon, after using the langauge for few months.

Let us get down the the actual under-the-hood difference (which will in a way explain all those tiny syntactic differences too on it's own).

Templates

Declaring templates in C++ is much like defining a macro, and telling the compiler to generate concrete classes each time the template is encountered.

So the moment the compiler encounters the code - Pair<int> it generates something along these lines -

class Pair_int {  
private:  
  int left, right;  
public:  
  Pair_int(int left, int right) {  
  this->left = left;  
  this->right = right;  
  }  
  int getLeft() { return left; }  
  int getRight() { return right; }  
};

Note: Pair_int is just a name I use to represent, there isn't any actual "Pair_int" named class generated. It could be a any randomly named class. It is in the compiled object code, so the name of the class anyway doesn't matter

Now, later on when it encounters Pair<float>, it generates this -

class Pair_float {  
private:  
  float left, right;  
public:  
  Pair_float(float left, float right) {  
  this->left = left;  
  this->right = right;  
  }  
  float getLeft() { return left; }  
  float getRight() { return right; }  
};

As a result, the more number of times you use the class Pair with different template type parameters, the object code keeps getting more and more bloated. It is in-fact well known that presence of widely used templates with too many type parameters causes executable bloat. But this also means that Pair<int> and Pair<float are truly different classes, and you get 100% type safety at runtime.

Generics

You can try writing this code -

Pair<String> s = new Pair("hello", "world");
Integer x = s.getLeft(); 

You might encounter an error of this type - s.getLeft() returns String incompatible with Integer x

Writing String sLeft = s.getLeft() will work perfectly fine. Thus we can see that compile-time type safety is available.

That said, in Java, generics are not much more than syntactic sugar. It does provide type-safety and type-checking at compilation level, but generic classes go through a process called type-erasure, which means all generics are based on Object class in Java - from which all non-primitive types extend.

Which means Java doesn't generate an entire class like PairInteger or PairString it instead just generates this -

static class Pair {
    private Object left, right;
    public Pair(Object left, Object right) {
    	this.left = left;
    	this.right = right;
    }
    public Object getLeft() {return left;}
    public Object getRight() {return right;}
}

What actually happens in the earlier example is this -

Pair s = new Pair("hello", "world");
Integer x = <String> s.getLeft(); // error
String sLeft = <String> s.getLeft(); // works

In Java, however many type parameters we use with Pair<...>, the generated byte-code contains only a single implementation of Pair that works with the Object datatype. This works perfectly fine, because all classes in Java extend from Object.

At Coding Blocks we mentor over 2000 aspiring software developers every year, teaching the fundamentals of Java and C++ among other things. If such intricacies of programming languages interest you, you might like to try out some of our online courses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment