where to find Implements!
Jonathan M Davis
jmdavisProg at gmx.com
Sun Oct 3 15:47:07 PDT 2010
On Sunday 03 October 2010 14:32:29 BLS wrote:
> On 30/09/2010 00:40, Jonathan M Davis wrote:
> > hich has the appropriate methods will work for any function that
> > requires a particular API on the type or types that it's dealing with.
> > There's no need to have any interfaces. They just work if they have the
> > correct API and don't work if they don't. The isRandomAccessRange()
> > template and its friends are used in template constraints to improve the
> > error messages you get when a type doesn't have the correct API, but
> > they aren't strictly-speaking necessary.
>
> Thanks you for taking the time to explain, Jonathan!
> It seems however, that these template constraints are just made for
> ranges. I maybe wrong but I can't see a general purpose value. I am also
> not able to see why an additional vtable entry is necessary to do
> ordinary /implements/ validation work. (Finally it is just a contract,
> which Mr compiler should negotiate)
>
> Bjoern
With an interface, you can declare a variable of that type. e.g.
interface I
{
//...
}
class C : I
{
//...
}
void main()
{
I i = new C();
}
The compiler then has to deal with the fact that a variable of an interface
could be of any class that implements that interface. That requires a vtable.
The duck typing that templates give you don't allow for anything of the sort.
You can't declare a variable of type RandomAccessRange.
A good example of templates and APIs would be a function that requires a
particular type be compareable with the less-than operator:
void func(T)(T a, T b)
{
//...
if(a < b)
//...
}
When func is instantiated with a given type, it will generate a copy of the
template with that type. If the generated code is valid, it will compile. If
it's not, it won't. So, func(5, 4) would create
void func!int(int a, int b)
{
//...
if(a < b)
//...
}
which as far as we can see with the code given is legal and works. However, what
if you have a struct S which doesn't implement opCmp() and therefore does not
have a less-than operator?
void func!S(S a, S b)
{
//..
if(a < b)
//...
}
The generated code won't compile because a < b is not valid. And the error
message may or may not be particularly clear. To improve the error message, we'd
add a template constraint
void func(T)(T a, T b)
if(__traits(compiles, a < b))
{
//...
if(a < b)
//...
}
Now the compiler won't even try and instantiate the template with a type that
can't be compared with less-than. It will complain about the type not matching
the template (because the template constraint isn't true). There is nothing here
about implementing any sort of interface. There is nothing here about declaring
variables of type CanBeComparedWithLessThan. There is no need for a vtable.
Now, that's a fairly simple example, but what if you have a more complex one,
that depending on the type either used == or <.
void func(T)(T a, T b)
if(__traits(compiles, a < b) ||
__traits(compiles, a == b))
{
//...
static if(__traits(compiles, a < b))
{
//...
if(a < b)
//...
}
//...
static if(__traits(compiles, a == b))
{
//...
if(a == b)
//...
}
//...
}
The code that is generated and what the function actually does could change
drastically depending on whether T has < or it has ==. You could even
intermingle additional static ifs and/or static ifs that required both < and ==,
so that the fact that T has both < and == could generate code that differed that
much more. With that sort of power, you can do stuff way more complicated than
require that a type have a particular function or set of functions. With an
interface, the set of functions is fixed. With templates, it could vary. You
could have two completely different set of APIs which both satisfy a particular
template and there fore both have the "interface" that that template is looking
for.
Templates are all about code generation. They aren't implementing any kind of
API or interface and they have no way to declare any kind of API in the same
sense that D interfaces do. A D interface (or Java or C# or whatever) gives an
_exact_ set of functions that a class implementing it must have in order to be
of that interface type. You can then declare variables of that interface.
templates, on the other hand, can use variable APIs, and you cannot declare
variables of whatever "interface" their API represents. They're purely code
generation.
And how would you declare an "interface" for a template to test whether a type
implements it? It's variable with the type. There could be several different APIs
which would satisfy a template. Sure you could try and declare multiple
interfaces, but you'd get a combinatorial explosion of them if you're talking
about very many different functions. It could get ugly fast - _especially_ with
ranges and all the different variations that you have there. Instead, all you
have to do is create template or function which is used in a template constraint
to check whether a given template argument will result in successful
instantiation of that template.
And really, what's worse about isForwardRange!(T) than is(T: ForwardRange) as
far as writing your programs that use them goes? You don't gain anything by
having the more generic syntax of is(T : ForwardRange). You'd still have to
declare interface ForwardRange somewhere. Instead of declaring an interface that
T is forced to implement, instead you declare a function which checks that T has
the functions that it's supposed to. It's not even really more work if you're
the one writing isForwardRange!() vs interface ForwardRange. It's just an
eponymous template instead of an interface.
The biggest difference is
Interfaces are for declaring types with a certain API and allow variables of
that type which use polymorphism.
Templates with template constraints generate code given a particular set of
template arguments as long as those template arguments result in the template's
constraint being true. There is no type declared or polymorphism involved.
- Jonathan M Davis
More information about the Digitalmars-d
mailing list