C#'s greatest mistakes

Jonathan M Davis jmdavisProg at gmx.com
Sun Nov 28 01:19:46 PST 2010


On Sunday 28 November 2010 00:17:30 Jérôme M. Berger wrote:
> Jonathan M Davis wrote:
> > On Saturday 27 November 2010 14:59:09 BLS wrote:
> >> On 27/11/2010 16:59, Torarin wrote:
> >>> 2010/11/27 Andrei Alexandrescu<SeeWebsiteForEmail at erdani.org>:
> >>>> We use template constraints for that kind of stuff.
> >>>> 
> >>>> Andrei
> >>> 
> >>> Yes, and that's great, but is there a way to check whether a template
> >>> argument matches a defined interface?
> >> 
> >> I could not resist..
> >> We should have Implements!
> >> 
> >> Luca has done some work on it.. but it does not compile anymore. However
> >> I think the intension is clear.
> >> http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.
> >> D&a rticle_id=101673
> > 
> > If you're checking whether it implements an interface, then : should work
> > just like it does with classes. If you're checking whether it has the
> > same functions that an interface requires, then you're not really using
> > interfaces correctly. structs don't implement interfaces. Classes do
> > that. So, either make it a class, or don't use interfaces. If you want
> > to verify that a struct has particular functions needed for the template
> > function in question, then just check whether calling them compiles (
> > e.g. __traits(compiles, foo.bar()) ). If you have a set of functions
> > that are expected to be there (for instance, to verify that the given
> > type is a certain type of range), then just create template which checks
> > for each of the functions that are supposed to be there and use the
> > template ( e.g. isForwardRange!T ). Interfaces and structs don't have
> > anything to do with each other.
> 
> 	Note that __traits(compiles, foo.bar()) does not check the return
> type. If you want to check the return and parameter types, you wind
> up with a very complex and ugly expression. This is very error
> prone. Look at the two examples below:
> 
> Using __traits(compiles...):
> ==============================8<------------------------------
> template isFooBar(T)
> {
>    enum bool isFooBar =
>       __traits (compiles,
>       {
>          T t;
>          int arg;
>          int r = t.foo (arg);
>       })
>       &&
>       __traits (compiles,
>       {
>          T t;
>          float arg;
>          bool r = t.bar (arg);
>       });
> }
> 
> template Test(T) if (isFooBar!T) ...
> ------------------------------>8==============================
> 
> Using a fictive "concept" keyword instead of "interface" to avoid
> confusions with true interfaces:
> ==============================8<------------------------------
> concept FooBar
> {
>    int  foo (int);
>    bool bar (float);
> }
> 
> template Test(T) if (is!FooBar (T)) ...
> ------------------------------>8==============================
> 
> 	The second one is clearly much easier to write and (especially)
> easier to read, which means:
>  - It is faster to write;
>  - It will contain a lot fewer errors and bugs (especially once you
> get into more complex concepts);
>  - It is immediately self-documenting.
> 
> 	Now, it might be possible to do this purely as a library with the
> current language if we are willing to re-use the "interface" keyword
> to define the concepts.

A clearer way to create such template constraints would definitely be nice. But 
aside from the fact that I'd absolutely hate to see interfaces be conflated with 
template constraints in this manner, you'd need a way to deal with the fact that 
the parameters and return types for such functions are frequently dependent on 
the type that the template is being instantiated with. A concept as you describe 
it would have to somehow take templated types into account in a way that 
interfaces can't. For instance, you could never define an interface which defined 
a forward range like isForwardRange!() does because the interface wouldn't be 
properly templatized.

What would probably be better would be a template which somehow took a function 
declaration (including properly using whatever template types that it's supposed 
to) and converted that into a proper template constraint. Something like

hasFunction!(T, @property ElementType!T front() const)

which converted it to a set of constraints using the appropriate templates from 
std.traits such as functionAttributes!(), ReturnType!(), and ParameterTypeTuple!
(). I'm not sure whether it's possible to do something like that right now (I 
don't think so, but maybe), but it's certainly possible to construct the pieces 
that from std.traits that it would translate into. You could then define a 
template such as isForwardRange!() which anded all of the hasFunction!() results 
for each function that the type is supposed to have to be a forward range.

- Jonathan M Davis


More information about the Digitalmars-d mailing list