www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - simple (I think) eponymous template question ... what is proper

reply james.p.leblanc <james.p.leblanc gmail.com> writes:
Evening All,

Eponymous templates allow a nice calling syntax.  For example, 
"foo" here can
be called without needing the exclamation mark (!) at calling 
sites.  We see that
foo is restricting a, and b to be of the same type ... so far, so 
good.

auto foo(T)(T a, T b) { ... }

Now, suppose I need to restrict "T" only certain subsets of 
variable types.

I can imagine putting in some "static if" statements in my 
function body,
is one solution.  (Also a bit ugly as the allowed types might 
grow).

Is there a more elegant way, to do this?

Regards,
James

PS Any violations should be caught at compile time.
Aug 17 2021
next sibling parent Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Tuesday, 17 August 2021 at 18:11:56 UTC, james.p.leblanc wrote:
 Is there a more elegant way, to do this?

 Regards,
 James

 PS Any violations should be caught at compile time.
That is template specialization: ``` auto moo(T : YourSpecialClassOrDType)(T myMoo) {•••} ``` You can also declare other overloads of the method, just make sure they don't overlap in the constraints, otherwise you'd get ambiguous call exception. Regards, Alexandru.
Aug 17 2021
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Aug 17, 2021 at 06:11:56PM +0000, james.p.leblanc via
Digitalmars-d-learn wrote:
 Evening All,
 
 Eponymous templates allow a nice calling syntax.  For example, "foo"
 here can be called without needing the exclamation mark (!) at calling
 sites.  We see that foo is restricting a, and b to be of the same type
 ... so far, so good.
 
 auto foo(T)(T a, T b) { ... }
 
 Now, suppose I need to restrict "T" only certain subsets of variable
 types.
This is exactly what "signature constraints" are for. For example: auto foo(T)(T a, T b) if (is(T == string)) // <--- this is a signature constraint { ... // deal with strings here } auto foo(T)(T a, T b) if (is(T == int)) // you can overload on signature constraints { ... // deal with ints here } auto foo(T)(T a, T b) if (is(T == float) || is(T == double)) // you can use complex conditions { ... // deal with floats or doubles here } Be aware that a signature constraint failure is not an error: the compiler simply skips that overload of the function. So if you make a mistake in a sig constraint, it may *always* fail, and the compiler may try instead to instantiate a completely unintended overload and generate an error that may have nothing to do with the actual problem. T -- Give a man a fish, and he eats once. Teach a man to fish, and he will sit forever.
Aug 17 2021
prev sibling next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 8/17/21 11:11 AM, james.p.leblanc wrote:

 auto foo(T)(T a, T b) { ... }

 Now, suppose I need to restrict "T" only certain subsets of variable 
types. There are template constraints: import std.traits; auto foo(T)(T a, T b) if (isArray!T) { // ... } auto foo(T)(T a, T b) if (isFloatingPoint!T) { // ... } void main() { foo(1.5, 2.5); } See __traits as well and of course you can use any compile-time check in the template constraint.
 I can imagine putting in some "static if" statements in my function body,
That method can display an intelligible error message if there is only one template implementation. Template constraints on the other hand, are not errors; they just determine what template implementations are available for instantiation for the used parameters. And of course, 'static if' has more uses other than just error reporting; which might be performed better with 'static assert'. Ali
Aug 17 2021
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 8/17/21 2:11 PM, james.p.leblanc wrote:
 Evening All,
 
 Eponymous templates allow a nice calling syntax.  For example, "foo" 
 here can
 be called without needing the exclamation mark (!) at calling sites.  We 
 see that
 foo is restricting a, and b to be of the same type ... so far, so good.
 
 auto foo(T)(T a, T b) { ... }
 
 Now, suppose I need to restrict "T" only certain subsets of variable types.
 
 I can imagine putting in some "static if" statements in my function body,
 is one solution.  (Also a bit ugly as the allowed types might grow).
 
 Is there a more elegant way, to do this?
[Template constraints](https://dlang.org/spec/template.html#template_constraints). -Steve
Aug 17 2021
parent reply james.p.leblanc <james.p.leblanc gmail.com> writes:
On Tuesday, 17 August 2021 at 18:28:53 UTC, Steven Schveighoffer 
wrote:
 On 8/17/21 2:11 PM, james.p.leblanc wrote:
 Evening All,
 
[Template constraints](https://dlang.org/spec/template.html#template_constraints). -Steve
Dear All, Thanks! I was aware of, and have used template constraints in simple ways. But, I should express my question a bit better: "... is there a more elegant way to expression these constraints?" Perhaps by extending Alexandru's "moo" concepts, with a list of allowed types: auto moo(T : (int || float || mySpecialStruct )(T myMoo) {•••} When re-using any such sets, it would be nice to define the set as follows: S = (int || float || mySpecialStruct) and then define "moo" more concisely as: auto moo(T < S)(T myMoo) {•••} ( where I have used "<" to mean "T is a member of S"). Possible? Best Regards, James
Aug 17 2021
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Aug 17, 2021 at 07:22:54PM +0000, james.p.leblanc via
Digitalmars-d-learn wrote:
[...]
 auto moo(T : (int || float || mySpecialStruct )(T myMoo) {•••}
 
 When re-using any such sets, it would be nice to define the set as
 follows:
 
 S = (int || float || mySpecialStruct)
 
 and then define "moo" more concisely as:
 
 auto moo(T < S)(T myMoo) {•••}
 
 ( where I have used "<" to mean "T is a member of S").
[...] You could use a helper template and an AliasSeq for this: template isAmong(T, S...) { static if (S.length == 0) enum isAmong = false; else enum isAmong = is(T == S) || isAmong(T, S[1..$]); } import std.meta : AliasSeq; alias MyTypes = AliasSeq!(int, float, MySpecialStruct); auto myFunc(T)(T a, T b) if (isAmong!(T, MyTypes)) { ... } T -- I am a consultant. My job is to make your job redundant. -- Mr Tom
Aug 17 2021
parent reply james.p.leblanc <james.p.leblanc gmail.com> writes:
On Tuesday, 17 August 2021 at 19:44:29 UTC, H. S. Teoh wrote:
 You could use a helper template and an AliasSeq for this:

 	template isAmong(T, S...) {
 		static if (S.length == 0)
 			enum isAmong = false;
 		else
 			enum isAmong = is(T == S) ||
 				isAmong(T, S[1..$]);
 	}

 	import std.meta : AliasSeq;
 	alias MyTypes = AliasSeq!(int, float, MySpecialStruct);

 	auto myFunc(T)(T a, T b) if (isAmong!(T, MyTypes)) { ... }


 T
Dear H.S. Teoh, Wow! That is absolutely beautiful ... I had never seen (or even imagined) a recursive template! This expands my mind in a good way ... and is going into my toolbox immediately. Best Regards, James
Aug 17 2021
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 17 August 2021 at 19:53:52 UTC, james.p.leblanc wrote:
 On Tuesday, 17 August 2021 at 19:44:29 UTC, H. S. Teoh wrote:
 You could use a helper template and an AliasSeq for this:

 	template isAmong(T, S...) {
 		static if (S.length == 0)
 			enum isAmong = false;
 		else
 			enum isAmong = is(T == S) ||
 				isAmong(T, S[1..$]);
 	}

 	import std.meta : AliasSeq;
 	alias MyTypes = AliasSeq!(int, float, MySpecialStruct);

 	auto myFunc(T)(T a, T b) if (isAmong!(T, MyTypes)) { ... }


 T
Dear H.S. Teoh, Wow! That is absolutely beautiful ... I had never seen (or even imagined) a recursive template! This expands my mind in a good way ... and is going into my toolbox immediately. Best Regards, James
FYI: in this particular case, you can use std.meta.staticIndexOf instead of writing the recursion out yourself: import std.meta: staticIndexOf; enum isAmong(T, S...) = staticIndexOf!(T, S) >= 0; Docs: https://phobos.dpldocs.info/std.meta.staticIndexOf.html
Aug 17 2021
parent reply james.p.leblanc <james.p.leblanc gmail.com> writes:
On Tuesday, 17 August 2021 at 20:13:59 UTC, Paul Backus wrote:

 FYI: in this particular case, you can use 
 std.meta.staticIndexOf instead of writing the recursion out 
 yourself:

     import std.meta: staticIndexOf;
     enum isAmong(T, S...) = staticIndexOf!(T, S) >= 0;

 Docs: https://phobos.dpldocs.info/std.meta.staticIndexOf.html
All, Thanks again ... Paul, I will try this staticIndexOf as you mention. I had been receiving "circular reference to variable" error messages from the "isAmong" suggestion. (But, I very likely had misunderstood how to use this.) I would still be interested to learn what I have done wrong. So, below is my code: import std.stdio; import std.meta : AliasSeq; template isAmong(T, S...) { static if (S.length == 0) enum isAmong = false; else enum isAmong = is(T == S) || isAmong(T, S[1..$]); } alias MyTypes = AliasSeq!(int, float); auto myFunc(T)(T a, T b) if (isAmong!(T, MyTypes)) { writeln(" in myFunc "); return; } void main(){ writeln("started ..."); auto a = 1; auto b = 2; myFunc!(int)(a, b); return; } And, here are the error message: (master) Notes > dmd recursive_template.d recursive_template.d(9): Error: circular reference to variable `recursive_template.isAmong!(int, int, float).isAmong` recursive_template.d(17): Error: template instance `recursive_template.isAmong!(int, int, float)` error instantiating recursive_template.d(29): while looking for match for `myFunc!int` Can anyone see what is going on? Best Regards, James
Aug 17 2021
parent Bastiaan Veelo <Bastiaan Veelo.net> writes:
On Tuesday, 17 August 2021 at 20:29:51 UTC, james.p.leblanc wrote:
 So, below
 is my code:

 import std.stdio;
 import std.meta : AliasSeq;

 template isAmong(T, S...) {
    static if (S.length == 0)
       enum isAmong = false;
    else
       enum isAmong = is(T == S) || isAmong(T, S[1..$]);
 }

 alias MyTypes = AliasSeq!(int, float);

 auto myFunc(T)(T a, T b) if (isAmong!(T, MyTypes)) {
   writeln(" in myFunc ");
   return;
 }

 void main(){
   writeln("started ...");
    auto a = 1;
    auto b = 2;
    myFunc!(int)(a, b);
    return;
 }


 And, here are the error message:

 (master) Notes > dmd recursive_template.d
 recursive_template.d(9): Error: circular reference to variable 
 `recursive_template.isAmong!(int, int, float).isAmong`
 recursive_template.d(17): Error: template instance 
 `recursive_template.isAmong!(int, int, float)` error 
 instantiating
 recursive_template.d(29):        while looking for match for 
 `myFunc!int`

 Can anyone see what is going on?

 Best Regards,
 James
https://run.dlang.io/is/m5svQ2 The error was in line 8. T (Teoh) forgot to take the first of `S` in `is(T == S[0])`. The error message improves after adding the `!` in `isAmong!(T, S[1..$])`, which, surprisingly, is optional! — Bastiaan.
Aug 17 2021
prev sibling next sibling parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Tuesday, 17 August 2021 at 19:53:52 UTC, james.p.leblanc wrote:
 Wow!  That is absolutely beautiful ... I had never seen (or even
 imagined) a recursive template!  This expands my mind in a good
 way ... and is going into my toolbox immediately.

 Best Regards,
 James
Just don't over rely on it. It can cause compilation slowdowns, so avoid it if you can.
Aug 17 2021
parent reply james.p.leblanc <james.p.leblanc gmail.com> writes:
On Tuesday, 17 August 2021 at 20:28:20 UTC, Alexandru Ermicioi 
wrote:
 On Tuesday, 17 August 2021 at 19:53:52 UTC, james.p.leblanc 
 wrote:
 Wow!  That is absolutely beautiful ... I had never seen (or 
 even
 imagined) a recursive template!  This expands my mind in a good
 way ... and is going into my toolbox immediately.

 Best Regards,
 James
Just don't over rely on it. It can cause compilation slowdowns, so avoid it if you can.
I've been happily trying to absorb all the helpful concepts posted. A final related question in the quest for simplicity and robustness. If I wanted to ensure that a function accepts only arguments of byte, int, uint, long, etc. (i.e. integer-like types). Is the accepted way to do this like so?: **auto foo( T : long )(T a, T b){ ... }** I really wish I could find a good explanation of the ":" (colon) used in templates. I am sure one exists ...but haven't come upon it just yet. (I thought "isIntegral" in traits module would be helpful...but I failed in my experiments.) Thanks for patience with this question! Best Regards, James
Aug 17 2021
next sibling parent Dominikus Dittes Scherkl <dominikus scherkl.de> writes:
On Wednesday, 18 August 2021 at 05:33:13 UTC, james.p.leblanc 
wrote:
 If I wanted to ensure that a function accepts only arguments of
 byte, int, uint, long, etc.  (i.e. integer-like types).  Is the 
 accepted way to do this like so?:

 **auto foo( T : long )(T a, T b){ ... }**
I very much prefer the ususal constraint syntax **auto foo(T)(T a, T b) if(isIntegral!T) { ... }**
Aug 17 2021
prev sibling parent reply Tejas <notrealemail gmail.com> writes:
On Wednesday, 18 August 2021 at 05:33:13 UTC, james.p.leblanc 
wrote:
 On Tuesday, 17 August 2021 at 20:28:20 UTC, Alexandru Ermicioi 
 wrote:
 On Tuesday, 17 August 2021 at 19:53:52 UTC, james.p.leblanc 
 wrote:
 Wow!  That is absolutely beautiful ... I had never seen (or 
 even
 imagined) a recursive template!  This expands my mind in a 
 good
 way ... and is going into my toolbox immediately.

 Best Regards,
 James
Just don't over rely on it. It can cause compilation slowdowns, so avoid it if you can.
I've been happily trying to absorb all the helpful concepts posted. A final related question in the quest for simplicity and robustness. If I wanted to ensure that a function accepts only arguments of byte, int, uint, long, etc. (i.e. integer-like types). Is the accepted way to do this like so?: **auto foo( T : long )(T a, T b){ ... }** I really wish I could find a good explanation of the ":" (colon) used in templates. I am sure one exists ...but haven't come upon it just yet. (I thought "isIntegral" in traits module would be helpful...but I failed in my experiments.) Thanks for patience with this question! Best Regards, James
A template specialization is basically a template overload. For example: ```d void func(int a){ writeln("argument is integer"); } void func(long a){ writeln("argument is long"); } void main(){ int a; long b; func(a); func(b); } ``` The above does what you expect. Now the template specialization way: ```d void funcTemplate(T:int)(T a){ writeln("argument is int"); } void funcTemplate(T : long)(T a){ writeln("argument is long"); } void main(){ int c; long d; funcTemplate(c); funcTemplate(d); } ``` The above will also do what you expect. Template specialization is basically overloading for templates, nothing more. Below is a complete working copy-paste example combining both: ```d import std; void func(int a){ writeln("argument is integer"); } void func(long a){ writeln("argument is long"); } void funcTemplate(T:int)(T a){ writeln("argument is int"); } void funcTemplate(T : long)(T a){ writeln("argument is long integer"); } void main(){ int a; long b; func(a); func(b); funcTemplate(a); funcTemplate(b); } ```
Aug 17 2021
parent james.p.leblanc <james.p.leblanc gmail.com> writes:
On Wednesday, 18 August 2021 at 06:53:51 UTC, Tejas wrote:

 void funcTemplate(T:int)(T a){
      writeln("argument is int");
 }

 void funcTemplate(T : long)(T a){
      writeln("argument is long integer");
 }

 void main(){
      int a;
      long b;
      func(a);
      func(b);

      funcTemplate(a);
      funcTemplate(b);

 }

 ```
Domninikus, Tejas, and All, I see that I had been (stupidly) omitting the exclamation point after **"isIntegral"** in my clumsy attempts to use the traits ... thanks for your clear example helping me identify my error. Also, thanks for mentioning the words **"template specialization"** , I did not know what to call the use of **":"** in templates. Now, I have a search term I can use to learn more ...and find it is in Phillippe Signaud's informative "D-templates-tutorial". Thanks again, James PS Also, I am enjoying, a entertaining and educational tutorial that Phillippe has linked in his tutorial. Other may learn from this as well: http://www.semitwist.com/articles/EfficientAndFlexible/SinglePage/
Aug 18 2021
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Aug 17, 2021 at 07:53:52PM +0000, james.p.leblanc via
Digitalmars-d-learn wrote:
 On Tuesday, 17 August 2021 at 19:44:29 UTC, H. S. Teoh wrote:
 You could use a helper template and an AliasSeq for this:
 
 	template isAmong(T, S...) {
 		static if (S.length == 0)
 			enum isAmong = false;
 		else
 			enum isAmong = is(T == S) ||
 				isAmong(T, S[1..$]);
 	}
 
 	import std.meta : AliasSeq;
 	alias MyTypes = AliasSeq!(int, float, MySpecialStruct);
 
 	auto myFunc(T)(T a, T b) if (isAmong!(T, MyTypes)) { ... }
[...]
 Dear H.S. Teoh,
 
 Wow!  That is absolutely beautiful ... I had never seen (or even
 imagined) a recursive template!  This expands my mind in a good
 way ... and is going into my toolbox immediately.
[...] I didn't want to spoil your joy of discovery, but since others have already done that -- beware of recursive templates, because if used too often they can become a source of big slowdowns to your compilation times (as well as the compiler consuming ridiculous amounts of memory). Simple, linearly-recursive templates like this one are probably harmless (unless you artificially generate pathologically-long lists of types). But if the recursion gets too deep or grows superlinearly, you probably want to consider alternative implementations instead. ;-) --T
Aug 17 2021