Possible solution to template bloat problem?
John Colvin
john.loughran.colvin at gmail.com
Tue Aug 20 11:20:15 PDT 2013
On Tuesday, 20 August 2013 at 17:59:35 UTC, H. S. Teoh wrote:
> On Tue, Aug 20, 2013 at 07:18:50PM +0200, Ramon wrote:
>> On Tuesday, 20 August 2013 at 17:05:21 UTC, Dicebot wrote:
>> >On Tuesday, 20 August 2013 at 16:59:00 UTC, Ramon wrote:
>> >>I'm afraid the issue is bigger.
>> >
>> >Your insight about variety of modern programming language
>> >applications is extremely limited. If you are willing to
>> >introduce
>> >random runtime costs for nothing, there are lot of other
>> >awesome
>> >languages that can satisfy your needs.
>> >
>> >As someone who does not want to write embedded'ish code in C
>> >anymore (and hopes to drag D there eventually) I am
>> >dangerously
>> >close to hating you.
>>
>> And that shows. Well, if that fits your definition of
>> "professional", so be it.
>>
>> To avoid misunderstandings: I still like D and think that it's
>> a
>> great language/solution. I still see very attractive points. I
>> still
>> think that even templates can be attractive and are way better
>> done
>> in D than in C++.
>>
>> I just don't agree that writing "generics" in the brochure and
>> actually delivering templates is a good thing. And I happen to
>> think
>> that real generics are a very good thing.
> [...]
>
> I think the problem is that your definition of "generic" is not
> the same
> as ours. :)
>
> Templates actually include your definition of generics if you
> use it
> correctly. Here's an example:
>
> interface LessThanComparable {
> bool opCmp(LessThanComparable b);
> }
>
> interface LessThanComparableRange {
> @property bool empty();
> @property LessThanComparable front();
> void popFront();
> }
>
> void sortRange(LessThanComparableRange range) {
> // Note: single template instantiation of sort here
> std.algorithm.sort(range);
> }
>
> class MyClass : LessThanComparable {
> override bool opCmp(LessThanComparable b) { ... }
> ...
> }
>
> class MyClassRange : LessThanComparableRange {
> override @property bool empty() { ... }
> override @property LessThanComparable front() { ... }
> override void popFront() { ... }
> }
>
> class MyOtherClass : LessThanComparable {
> override bool opCmp(LessThanComparable b) { ... }
> ...
> }
>
> class MyOtherClassRange : LessThanComparableRange {
> override @property bool empty() { ... }
> override @property LessThanComparable front() { ... }
> override void popFront() { ... }
> }
>
> void main() {
> MyClassRange firstRange = ...;
> MyOtherClassRange secondRange = ...;
> ...
> sortRange(firstRange);
> sortRange(secondRange);
> }
>
> A few notes:
>
> - LessThanComparable lets you do polymorphism at runtime, so
> sortRange
> is a non-template "real generic" function that can sort any
> range
> involving LessThanComparable.
>
> - LessThanComparableRange provides a single ABI for any range of
> LessThanComparable elements.
>
> - sortRange instantiates the template function
> std.algorithm.sort
> exactly *once*, and it will work with any runtime type that
> implements
> LessThanComparableRange.
>
> - The rest of the code shows how you can define a bunch of
> classes that
> implement these interfaces, and they can be used with the
> sortRange
> function with full runtime polymorphism.
>
> You will note that there's some amount of boilerplate here --
> because I
> just typed this up off the top of my head for illustration
> purposes; in
> real code you'd use mixins or other such stuff, perhaps *cough*
> use a
> template for generating all the xxxRange classes *cough*
> automatically.
>
> So this lets you have "real generics" which, as you can see, is
> really
> just a subset of what is covered by the template
> std.algorithm.sort,
> which can handle *any* concrete type, even those that don't
> implement
> any runtime polymorphic interfaces.
>
> Now let's talk about about how templates and "real generics"
> can work
> together.
>
> If you think about the above code carefully, you will realize
> that, at
> the machine level, you cannot avoid the overhead of
> dereferencing
> pointers to the various interfaces and class vtables, because
> the CPU
> can only deal with concrete types, it doesn't know how to work
> with data
> that can be any type. So, at *some* level, whether visible at
> the code
> level or not, everything must be translated down to
> type-specific code
> that the CPU can actually run. In "real generics", this is
> accomplished
> by having a single ABI (the interfaces LessThanComparable and
> LessThanComparableRange) that all concrete types must
> implement. Once
> implemented, the sortRange function doesn't have to deal with
> concrete
> types anymore: it can express the sorting algorithm directly in
> terms of
> the interfaces, and when a concrete operation like '<' is
> desired, it
> invokes LessThanComparable's opCmp method to accomplish it.
> (Which, in
> turn, essentially dereferences a function pointer that points
> to the
> actual machine code that does the comparison of the concrete
> type.)
>
> This, of course, has a lot of runtime costs: you need space to
> store the
> vtables, you need to initialize the pointers to these vtables
> every time
> you create a new instance of a class, it requires runtime
> overhead for
> calling a function via a function pointer instead of directly,
> etc.. The
> advantage, though, is that it allows sortRange to be "real
> generic", in
> the sense that you can load up a dynamic library at runtime
> that returns
> a range of elements of unknown type, and sortRange will be able
> to sort
> it just by using the LessThanComparable* interfaces.
>
> Now let's think about the template version of sortRange, which
> is just
> std.algorithm.sort. Here, the binding to the concrete type
> happens at
> compile-time; rather than produce a single machine code for
> sortRange
> that handles polymorphism via indirection (interfaces, function
> pointers, etc.), the template produces code that sorts a range
> of a
> *specific* type. Since we know exactly what concrete types
> we're dealing
> with, we don't need any of the indirections of the "real
> generic"
> approach; the compiler's optimizer can take advantage of
> characteristics
> of the concrete type to produce the most optimized machine code
> for
> sorting ranges of that kind. Each instantiation of
> std.algorithm.sort
> produces optimal machine code for sorting that particular
> range, because
> all the bindings to the concrete type is done at compile-time
> rather
> than runtime. This gives you the top performance.
>
> Of course, in programming, nothing comes for free; so the price
> for this
> top performance is that you need to produce many copies of the
> sort
> function -- one for each type of range, each optimized for that
> particular type of range. And on the surface, it would appear
> that it
> would be unable to handle runtime polymorphism either, because
> the
> template must be bound to the concrete types at compile-time.
> Right?
>
> Actually, this is where the power of templates is shown: in the
> example
> code I gave above, I deliberately implemented sortRange with
> std.algorithm.sort. But actually, the way I wrote it is kind of
> unnecessary, because std.algorithm.sort can be instantiated
> *directly*
> with LessThanComparableRange! So actually, we don't even need
> sortRange
> at all -- we can just call std.algorithm.sort directly on our
> "real
> generic" containers, and the compiler will produce a "real
> generic"
> version of sort that has all the runtime indirections that I
> described
> above, for handling runtime polymorphic objects,
> dynamically-loaded
> objects, etc..
>
> Armed with this insight, we can now see that we can actually
> have the
> best of both worlds: if we already know the concrete types at
> compile-time, the template system optimizes the sort function at
> compile-time to deal specifically with that concrete type --
> you get
> optimal performance. But if we don't know the concrete type at
> compile-time, we create an interface to be implemented by future
> concrete types (say by a 3rd party vendor), and the template
> system
> produces a "real generic" version of sort that can handle
> runtime
> polymorphism.
>
> IOW, the templating system *includes* what you describe as "real
> generics"; it is not inferior to it at all! In fact, the
> templating
> system can do what "real generics" can't: produce optimal code
> for a
> specific type without needing to copy-n-paste code (either
> manually or
> with an IDE or whatever). It's the *compiler* that instantiates
> the
> template with the concrete type, so it has access to all the
> specific
> implementation details of said concrete type which it can use
> to produce
> the best machine code for it -- automatically.
>
>
> T
Yet again, D proves to be a powerful enough language to not need
extra language extensions to support a wide variety of paradigms.
We should have some template mixins for this stuff in
std.patterns or something.
More information about the Digitalmars-d
mailing list