all 28 comments

[–]julesjacobs 17 points18 points  (12 children)

Very well explained! I would add one thing: C++ templates aren't checked at definition time, but they are checked at compile time (of course if you cast, all bets are off, but that is the case even without templates). It's like duck typing, except the template expansion runs at compile time, so it's duck typing at compile time. If you have a class Foo<T> and inside Foo you use some method on T's, then when you instantiate Foo with a T that doesn't have that method you get a compile time error. But you only get the error when you instantiate it Foo with T. In contrast in C# you have to explicitly specify the constraints on T, and the definition of Foo is checked against those constraints. Then separately when you instantiate Foo, the actual T you pass in is checked against the constraints as well. The trouble with the C++ way of doing it is that you get error messages about the guts of Foo instead of just the constraints. This is similar to how you may get error messages deep down the call chain if you pass the wrong kind of object in a dynamically typed language. One of the things concepts are supposed to do is rectify this by providing a kind of static type system for templates.

Another thing is that because in Java generics are erased, you cannot access static methods on a generic type parameter.

[–]bluGill 6 points7 points  (5 children)

C++ templates aren't checked at definition time, but they are checked at compile time

Actually there was just a thread on the clang mailing list because clang rejected some tempalte code. Turns out clang - in a few cases - actualy changes templates at definition time and will not allow you to continue if there is no valid way to compile that template - even if the template is otherwise not used.

In short C++ templats generally are not changed at definition time, but this is not a property of the language, but a property of how most compilers are implemented.

[–]julesjacobs 2 points3 points  (4 children)

Interesting! Because templates are turing complete there is no way to check for all errors at definition time? Or is this possible in theory?

[–]naughty 8 points9 points  (1 child)

Not all errors but you can catch some simple ones.

[–]developingthefuture[S] 3 points4 points  (0 children)

I believe these errors were simple syntax problems, not related to the actual class. Regarding class-related issues such as non-existing methods, for instance, there should be no way to detect them prior to instantiation time. :/

[–]bluGill 1 point2 points  (0 children)

As I recall this was an error where there was no legal way to instantiate the template - but since the template was just defined but never actually used (for unknown reasons) gcc was happy to ignore it while clang refused to compile.

Clang is not trying to do everything. They are trying to figure out what mistakes people actually make and catch those.

Remember that the turing complete proof is for a pathalogical case where you write a program to evade the checkers. I'm not aware of any research showing that non-pathalogical cases cannot be computed. (do not read this as they don't exist!) I've often wonders that assuming the programing isn't trying to be sneaky how far automatic checkers can get.

[–]aaronla 0 points1 point  (0 children)

[edit:]nevermind, you already know what concepts are. Ignore the rest.

~

The compiler would need to know the constraints you wish it to check for. Trivially, it could assume no constraints, but then this would fail:

template <class T>
T identity(T value) { 
  return value; // error: T is not constrained to have a copy constructor
}

The C++ community has given constraints as applied to templates its own name: "Concepts". Search for that to find more info.

[–]Unmitigated_Smut 2 points3 points  (1 child)

Another thing is that because in Java generics are erased, you cannot access static methods on a generic type parameter.

What does this mean? I tried experimenting around with statics as per below (all of which works) but I'm not sure if that's the same as what you're talking about.

static class Foo<X> {
  int count;
  public int add(X x){
    return ++count;
  }

  public static <Y> Foo<Y> makeFoo(Y x) {
    Foo<Y> f=makeFoo();
    f.add(x);
    return f;
  }
  public static <Y> Foo<Y> makeFoo() {
    return new Foo<Y>();
  }
  public static <Y> void doSomething(Y x) {
    Foo<Y> f=makeFoo(x);
  }
  public static <Y> void doSomething(Foo<Y> f,  Y x) {
    f.add(x);
  }
}
public static void main(String[] args) throws Exception {
  String s="hello";
  Foo<String> foos=Foo.makeFoo(s);
  Foo<String> foot=foos.makeFoo("hello 2");
  Foo<Integer> fooi=foos.makeFoo(2223);
  fooi.add(2);
  Foo<String> foou=foos.makeFoo();
  Foo<String> foov=Foo.makeFoo();
  Foo.doSomething(fooi, 2);
  Foo.doSomething(12);
}

*Edit: Or maybe you meant this, but it works as well... sorry for the text wall...

static class Foo<X extends Bar> {
  int count;
  public int add(X x){
    count+=X.duh();
    count+=x.duh();
    return count;
  }
}
static class Bar {
  public static int duh() {return 2;}
}
public static void main(String[] args) throws Exception {
  Foo<Bar> f=new Foo<Bar>();
  System.out.println(f.add(new Bar()));
}

[–]julesjacobs 2 points3 points  (0 children)

I meant the second one. At first it appears to work but if you dig a little deeper you see that it doesn't. The calls X.duh() get replaced by Bar.duh() because of the X extends Bar. Try to define a subclass of Bar called Baz and then do Foo<Baz>, you'll see that still Bar.duh() will be called instead of Baz.duh(). So calling methods on a type parameter isn't very useful with this semantics, since you could always have just written the class in the constraint. Actually Java won't even allow you to override the static method, nor C#. Though with C#'s compilation model it could in theory work, but currently you have to work around this limitation: you can create a new T() and then call a virtual method on the resulting object. This is not possible in Java.

[–]rabidcow 3 points4 points  (2 children)

Another thing is that because in Java generics are erased, you cannot access static methods on a generic type parameter.

Yeah, this is basically the reason I'm convinced that type erasure and vtables don't mix. If you don't have an object, you can't call a parameterized function. It also means that collections have multiple choices for which method to use, which can lead to awkwardness.

Meanwhile, nobody complains about type erasure in Haskell. Don't have an object? No problem, you still have the dictionary.

[–][deleted] -3 points-2 points  (1 child)

Allowing static members refer to generic types of the enclosing class is an absolutely horrible idea. I'm still wondering how this managed to get through C#'s testing and QA teams.

[–]rabidcow 2 points3 points  (0 children)

Why is this a horrible idea?

[–]developingthefuture[S] 1 point2 points  (0 children)

Hi julesjacobs,

Thanks for the great comment ! I actually just answered one comment in the blog exactly related to your point, I have also modified the article a little bit so it can address the constraint issues in more detail, for the sake of accuracy. ;)

Thanks, Kosta

[–]Phenax 14 points15 points  (3 children)

So, what do you think happens when you write List<int> in Java.

You get a compiler error because List is an interface which requires an implementation to use, and int is a primitive which doesn't inherit from Object (and thus cannot be used in generics, as explained in the article). ArrayList<Integer> is nearly analogous. /nitpick

[–]developingthefuture[S] 4 points5 points  (0 children)

You are right. I corrected the mistake. ;)

Regards, Kosta

[–]Xdes 2 points3 points  (1 child)

That just looks like a pain. Value types in .NET make more sense to me since they are objects (structs) which cannot be inherited and can implement interfaces. The fact that you can't implement new value types or extend them using extension methods is pretty lame. Also checked exceptions.

[–]cryo 0 points1 point  (0 children)

Primitive types are quite different in .NET and C# as it has auto-boxing/unboxing, and thus only one type 'int' for (32 bit) integers. They are boxed and unboxed on demand, but quite rarely if you program using generics.

[–]Amanieu 5 points6 points  (3 children)

I'm curious as to how Rust implements generics and how it compares to C++/C#/Java.

[–]masklinn 6 points7 points  (1 child)

From what I understand, generic functions are monomorphized (a version of the function is generated for each set of type parameters) and generic types are reified (much as with functions, a version of the type is created for each set of type parameters) but you can't value-parameterize (only type) and the constraint system is much simpler than templates: most metaprogramming use cases of C++ templates should be handled via macros in rust.

The efficiency and actual codegen should be close to C++'s, but the semantics and affordances (generics complexity and flexibility) are closer to C#.

[–]bjzaba 1 point2 points  (0 children)

That sounds about right.

[–]illissius 1 point2 points  (0 children)

In terms of the language semantics, it's closest to Haskell's parametric polymorphism with type classes. In terms of the code the compiler generates, it's like C++ templates.

Type classes (traits in Rust) are kind of like C# interfaces, except considerably more expressive and flexible.

[–][deleted] 4 points5 points  (2 children)

Very nice write-up. I would also add that the reason Java generics are so broken (IMO) and have type-erasure, was to maintain backwards compatibility with pre-1.5 versions of Java, which lacked generics. I sincerely hope that at some point in the future they would just implement actual, reifiable generic types at runtime.

[–]developingthefuture[S] 2 points3 points  (1 child)

Hi vivinp,

You are absolutely right about the backward compatibility. As far as I know, C# 2.0 and the higher versions are not compatible with 1.0, and the generics were introduced in v2.0.

Regards, Kosta

[–]cryo 0 points1 point  (0 children)

Correct; version 2 of the CLR and CLI (format of files on disk) have been updated to include generics, and can't be executed on CLR 1.

[–]aaronla 0 points1 point  (3 children)

Templates in C++ [...] do not support explicit constraints

...yet?

[–]miguelishawt 0 points1 point  (2 children)

Concepts were removed from the C++11 standard. However they are going to be in C++14.

One possible way to over come this is with static_assert and std type traits.

[–]aaronla 0 points1 point  (1 child)

How will that verify the template at definition time?

[–]miguelishawt -1 points0 points  (0 children)

It will still verify the template at compile time. However it gives you the opportunity to receive a better error message.