www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - alias = compile-time variants?

reply Jason Spencer <spencer8 sbcglobal.net> writes:
In writing templates that make heavy use of alias parameters, does
anyone else feel uneasy about whether the caller will pass a type, a
value, or a schmoo?  I'm having a hard time getting my head around
how wide-open aliases are and trying to resist the urge to put in
thousands of static asserts to check what should be invariants on
the kind of thing that can be passed in that position.  This all
feels very strange when I typically look for templates to provide
that nice, static, compile-time safety that keeps me from having to
check everything that gets passed.  Any words of wisdom on adjusting
to this feature?

Thanx,
Jason
Jul 29 2010
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Jason Spencer:
 In writing templates that make heavy use of alias parameters, does
 anyone else feel uneasy about whether the caller will pass a type, a
 value, or a schmoo?
Normally D is a mostly statically typed language (objects have a dynamic type), but sometimes template juggling is purely dynamically typed. See also: http://d.puremagic.com/issues/show_bug.cgi?id=3881 Being able to avoid the 'alias' and pass structs, templates and arrays (curiously char/wchar/dchar arrays are allowed, but not other types of arrays) to templates can be positive to avoid some troubles caused by alias template arguments. Bye, bearophile
Jul 29 2010
prev sibling parent reply Philippe Sigaud <philippe.sigaud gmail.com> writes:
On Thu, Jul 29, 2010 at 22:40, Jason Spencer <spencer8 sbcglobal.net> wrote:

 In writing templates that make heavy use of alias parameters, does
 anyone else feel uneasy about whether the caller will pass a type, a
 value, or a schmoo?
I thought they could only be symbols. That is, an alias is a 'link', a sort of pointer to a symbol: a template name, a module name, a function name, etc. It does not seem to accept types: template TestAlias(alias a) { enum string TestAlias = __traits(identifier, a); } void main() { T foo(T)(T t) { return t;} writeln(TestAlias!foo); // works writeln(TestAlias!int); // do not compile } Though '3' is accepted by alias... Hmm.
 I'm having a hard time getting my head around
 how wide-open aliases are and trying to resist the urge to put in
 thousands of static asserts to check what should be invariants on
 the kind of thing that can be passed in that position.  This all
 feels very strange when I typically look for templates to provide
 that nice, static, compile-time safety that keeps me from having to
 check everything that gets passed.  Any words of wisdom on adjusting
 to this feature?
Wisdom, I don't know, as I still feel like I'm exploring things. But template constraints are there to limit what you can instantiate. The C++ syntax is still there (T : U, etc), but it's quite limited and not so easy to parse, compared to D template constraints. Feel free to ignore what follows if that's well-treaded ground to you. Say I have a template that takes an alias, fun, and a type, T. fun is supposed to be a function, but in fact why limit it to that? What I need is for foo to be callable with a T. So let's test for that: auto doSomething(alias fun, T)(T t) if (is(typeof( fun(T.init) ))) { // now that I'm here, I can freely use fun as a callable on any T auto result = fun(t); // ... } So, T is a type. T.init is a value of type T, the default value for any T. .init is a easy way to generate a value from a type. Any type in D has a .init field (not typetuples, though). Note that 't' is not really accessible inside the constraint template, because t will have a runtime value, whereas here we are at compile-time. I think t can be used, but is still T.init at this stage. I call fun with T.init. If that compiles, then the resulting expression has a type: I use typeof() to get it, and then the is() expression to get a bool telling me if that's a valid type. If foo(T.init) doesn't compile, for whatever reason (fun as no opCall defined, or foo is callable, but not with a T...) then foo(T.init) has no type, so is(typeof( ... )) returns false and the template constraint forbids the template to be instantiated. inside doSomething(), I can freely use fun as something callable with one T. Note that my constraint is not complete: I did not test for fun(T.init) to return a value: it could be a void function(T) and the assignement would fail. I could also test for that, I guess. Philippe
Jul 30 2010
parent reply Jason Spencer <spencer8 sbcglobal.net> writes:
== Quote from Philippe Sigaud (philippe.sigaud gmail.com)'s article
 --0016e6d58a039d35e2048c9aa7e2

 I thought they could only be symbols. That is, an alias is a 'link',
a sort
 of pointer to a symbol: a template name, a module name, a function
 name, etc.
Whatever confidence you inspired by removing type from the list is quickly lost and more when you add module name--I hadn't thought of that! :)
 Wisdom, I don't know, as I still feel like I'm exploring things. But
 template constraints are there to limit what you can instantiate.
 ...
 Say I have a template that takes an alias, fun, and a type, T.
 fun is supposed to be a function, but in fact why limit it to that?
 What I need is for foo to be callable with a T. So let's test for
 that:
 auto doSomething(alias fun, T)(T t)
 if (is(typeof( fun(T.init) )))
 {
 // now that I'm here, I can freely use fun as a callable on any T
  auto result = fun(t);
 // ...
 }
I understand this example, and (most of) the mechanics of constraints. What I'm not so sure about is the recommended practice around their use. I see lot's of code that doesn't check those things. Suppose you left off the constraint and did: class Foo(U){} doSomething!(Foo, int)(3) it seems like there's a good chance you could get: auto result = Foo!(int); // type inferred from 3 (since this doesn't actually work like I'm saying, please conveniently imagine a similar case that does. :) Even with your constraint, I'm not sure I feel any more comfortable. If it compiles in the body of doSomething, it will compile in the constraint--not sure I've added any value. So how do you sleep at night not knowing if there's some funky syntax on somebody's template-that-takes-a-template which, when combined with some inference, might look like your function call on a value param? My initial reaction is to specify the hell out of the constraints, but I couldn't beat the feeling I was going overboard. I suspect that most people rely on the fact that most improper calls won't compile. Maybe I'm still too new to the syntax to have a good feel for what will get caught, and what could interpreted by the compiler in multiple ways depending on the actual arguments. So, do folks write constraints to ensure that modules don't get passed to their templates? :) Jason
Jul 31 2010
parent reply Lutger <lutger.blijdestijn gmail.com> writes:
Jason Spencer wrote:

 == Quote from Philippe Sigaud (philippe.sigaud gmail.com)'s article
 --0016e6d58a039d35e2048c9aa7e2

 I thought they could only be symbols. That is, an alias is a 'link',
a sort
 of pointer to a symbol: a template name, a module name, a function
 name, etc.
Whatever confidence you inspired by removing type from the list is quickly lost and more when you add module name--I hadn't thought of that! :)
 Wisdom, I don't know, as I still feel like I'm exploring things. But
 template constraints are there to limit what you can instantiate.
 ...
 Say I have a template that takes an alias, fun, and a type, T.
 fun is supposed to be a function, but in fact why limit it to that?
 What I need is for foo to be callable with a T. So let's test for
 that:
 auto doSomething(alias fun, T)(T t)
 if (is(typeof( fun(T.init) )))
 {
 // now that I'm here, I can freely use fun as a callable on any T
  auto result = fun(t);
 // ...
 }
I understand this example, and (most of) the mechanics of constraints. What I'm not so sure about is the recommended practice around their use. I see lot's of code that doesn't check those things. Suppose you left off the constraint and did: class Foo(U){} doSomething!(Foo, int)(3) it seems like there's a good chance you could get: auto result = Foo!(int); // type inferred from 3 (since this doesn't actually work like I'm saying, please conveniently imagine a similar case that does. :)
Sure, but that would be quite a gotcha since you went from calling something to merely instantiating a template. Perhaps there are such gotcha's, I am not aware of them.
 Even with your constraint, I'm not sure I feel any more comfortable.
 If it compiles in the body of doSomething, it will compile in the
 constraint--not sure I've added any value.
Perhaps not in this case, but: - constraints add documentation, such as isInputRange!T or IsCallable!fun - you can add a constraint that may not be statically checked inside the body of the template. This way you can still reject template parameters considered to be invalid even if the template body *could* be instantiated with them. - a constraint is a nice place to add better compiler error messages
 So how do you sleep at night not knowing if there's some funky syntax
 on somebody's template-that-takes-a-template which, when combined with
 some inference, might look like your function call on a value param?
 My initial reaction is to specify the hell out of the constraints, but
 I couldn't beat the feeling I was going overboard.  I suspect that
 most people rely on the fact that most improper calls won't compile.
 Maybe I'm still too new to the syntax to have a good feel for what
 will get caught, and what could interpreted by the compiler in
 multiple ways depending on the actual arguments.
 
 So, do folks write constraints to ensure that modules don't get passed
 to their templates?  :)
 
 Jason
Why not? As long as your module does the right thing, it may be used to instantiate my template :)
Jul 31 2010
parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
On Sat, Jul 31, 2010 at 21:17, Lutger <lutger.blijdestijn gmail.com> wrote:

 Jason Spencer wrote:

 == Quote from Philippe Sigaud (philippe.sigaud gmail.com)'s article
 --0016e6d58a039d35e2048c9aa7e2

 I thought they could only be symbols. That is, an alias is a 'link',
a sort
 of pointer to a symbol: a template name, a module name, a function
 name, etc.
Whatever confidence you inspired by removing type from the list is quickly lost and more when you add module name--I hadn't thought of that! :)
I discovered it by error, IIRC. That and the fact that template-like statements like auto members = __traits(allMembers, std.stdio); work. Try it, print the result. Though I do not know what to do with it :)
 Wisdom, I don't know, as I still feel like I'm exploring things. But
 template constraints are there to limit what you can instantiate.
 ...
 Say I have a template that takes an alias, fun, and a type, T.
 fun is supposed to be a function, but in fact why limit it to that?
 What I need is for foo to be callable with a T. So let's test for
 that:
 auto doSomething(alias fun, T)(T t)
 if (is(typeof( fun(T.init) )))
 {
 // now that I'm here, I can freely use fun as a callable on any T
  auto result = fun(t);
 // ...
 }
I understand this example, and (most of) the mechanics of constraints. What I'm not so sure about is the recommended practice around their use. I see lot's of code that doesn't check those things. Suppose you left off the constraint and did: class Foo(U){} doSomething!(Foo, int)(3) it seems like there's a good chance you could get: auto result = Foo!(int); // type inferred from 3 (since this doesn't actually work like I'm saying, please conveniently imagine a similar case that does. :)
Sure, but that would be quite a gotcha since you went from calling something to merely instantiating a template. Perhaps there are such gotcha's, I am not aware of them.
You can test for the alias to be a function (is(typeof(a) == function)), a delegate, or you can test for the presence of an opCall operator (the '()' operator), with __traits(hasMember, alias, "opCall") I think there should be a isCallable trait in Phobos... But in the opCall case, the gotcha is the class templates are not classes. Only instantiated classes are really classes. That is, given class C(T) { T t; T opCall(T u) { return t;} } __traits(hasMember, C, "opCall") will answer 'false'. That's normal, since C is not a class, but a template, of type void. __traits(hasMember, C!int, "opCall") will answer 'true'.
 Even with your constraint, I'm not sure I feel any more comfortable.
 If it compiles in the body of doSomething, it will compile in the
 constraint--not sure I've added any value.
Perhaps not in this case, but: - constraints add documentation, such as isInputRange!T or IsCallable!fun - you can add a constraint that may not be statically checked inside the body of the template. This way you can still reject template parameters considered to be invalid even if the template body *could* be instantiated with them.
I never thought of that.
 - a constraint is a nice place to add better compiler error messages
Except the compiler just say it cannot instantiate the template NameWithConstraints.
 So how do you sleep at night not knowing if there's some funky syntax
 on somebody's template-that-takes-a-template which, when combined with
 some inference, might look like your function call on a value param?
 My initial reaction is to specify the hell out of the constraints, but
 I couldn't beat the feeling I was going overboard.  I suspect that
 most people rely on the fact that most improper calls won't compile.
Yes.
 Maybe I'm still too new to the syntax to have a good feel for what
 will get caught, and what could interpreted by the compiler in
 multiple ways depending on the actual arguments.
I have nothing against long and well-documented constraints. I feel we are not using them as much as we could. With things like staticMap and such, you can do a lot!
 So, do folks write constraints to ensure that modules don't get passed
 to their templates?  :)

 Jason
Why not? As long as your module does the right thing, it may be used to instantiate my template :)
You can try to define an isTemplate template, and a isModule template :-) Look at this: T foo(T)(T t) { return t;} class C(T) { T t; T opCall(T u) { return t;} } template Describe(alias a) { enum string Name = a.stringof; enum string Ident = __traits(identifier, a); } void main() { writeln(Describe!(foo).Name); // foo(T) writeln(Describe!(foo).Ident); // foo writeln(Describe!(C).Name); // C(T) writeln(Describe!(C).Ident); // C writeln(Describe!(C!int).Name); // C (really, I thought that would be C!(int). I remember devising a way to extract the types from a template writeln(Describe!(C!int).Ident); // C writeln(Describe!(Describe).Name); // Calling it on itself! Describe(alias a) writeln(Describe!(Describe).Ident); // Describe writeln(Describe!(std.stdio).Name); // module stdio Strange, no std. in sight. writeln(Describe!(std.stdio).Ident); // stdio } So, inside a template a.stringof gives "module a" if a is a module, except it seems to cut anything before a dot. I do not like relying on .stringof, because it's undocumented and not always perfectly coherent. But a pragmatic implementation of isModule could be: template isModule(alias symbol) { enum bool isModule = (symbol.stringof[0..7] == "module "); } :-) Philippe
Aug 01 2010