www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Template Usage with Eponymous Trick

reply ShadoLight <ettienne.gilbert gmail.com> writes:
Taking this example from documentation page on 'Template Sequence 
Parameters' [1]:

import std.stdio;

template print(args...)
{
     void print()
     {
         writeln("args are ", args); // args is a ValueSeq
     }
}

template write(Args...)
{
     void write(Args args) // Args is a TypeSeq
                           // args is a ValueSeq
     {
         writeln("args are ", args);
     }
}

void main()
{
     print!(1,'a',6.8).print();                    // prints: args 
are 1a6.8
     write!(int, char, double).write(1, 'a', 6.8); // prints: args 
are 1a6.8
}

This fails to compile with:
onlineapp.d(22): Error: template onlineapp.print cannot deduce 
function from argument types !()(void), candidates are:
onlineapp.d(3):        print(args...)()
onlineapp.d(23): Error: function onlineapp.write!(int, char, 
double).write(int _param_0, char _param_1, double _param_2) is 
not callable using argument types ()

_param_0

Fixing the error is simply to use 'simplified' template calling 
convention for templates based on the 'Eponymous Trick':

     print!(1,'a',6.8)();                    // prints: args are 
1a6.8
     write!(int, char, double)(1, 'a', 6.8); // prints: args are 
1a6.8

Why does the 'classical' template calling convention not work 
anymore in this case? (if the template name and function name are 
different it obviously still works). Note the templates were not 
defined in the simplified 'Eponymous Trick' style i.e.:

     void print(args...)()
     {
         writeln("args are ", args); // args is a ValueSeq
     }


     void write(Args...)(Args args) // Args is a TypeSeq
                                    // args is a ValueSeq
     {
         writeln("args are ", args);
     }

...but in the 'classical' default template style, so I would have 
thought the 
template_name!(compile_time_args).function_name(run_time_args) 
style would still work, even if the template and function names 
are identical.

If this is in fact now the intended behavior, then there are some 
places where the documentation are in error.


[1]: https://dlang.org/spec/template.html#variadic-templates
Jan 30 2020
next sibling parent ShadoLight <ettienne.gilbert gmail.com> writes:
On Thursday, 30 January 2020 at 14:10:38 UTC, ShadoLight wrote:
 Taking this example from documentation page on 'Template 
 Sequence Parameters' [1]:

 [...]
Tested on https://run.dlang.io
Jan 30 2020
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Thursday, 30 January 2020 at 14:10:38 UTC, ShadoLight wrote:
 ...but in the 'classical' default template style, so I would 
 have thought the 
 template_name!(compile_time_args).function_name(run_time_args) 
 style would still work, even if the template and function names 
 are identical.

 If this is in fact now the intended behavior, then there are 
 some places where the documentation are in error.


 [1]: https://dlang.org/spec/template.html#variadic-templates
Eponymous templates are documented here: https://dlang.org/spec/template.html#implicit_template_properties This specific behavior is documented in the first paragraph below that heading, which reads as follows:
 If a template contains members whose name is the same as the 
 template identifier and if the type or the parameters type of 
 these members include at least all the template parameters then 
 these members are assumed to be referred to in a template 
 instantiation
Jan 30 2020
parent reply ShadoLight <ettienne.gilbert gmail.com> writes:
On Thursday, 30 January 2020 at 14:22:11 UTC, Paul Backus wrote:
 On Thursday, 30 January 2020 at 14:10:38 UTC, ShadoLight wrote:
 ...but in the 'classical' default template style, so I would 
 have thought the 
 template_name!(compile_time_args).function_name(run_time_args) 
 style would still work, even if the template and function 
 names are identical.

 If this is in fact now the intended behavior, then there are 
 some places where the documentation are in error.


 [1]: https://dlang.org/spec/template.html#variadic-templates
Eponymous templates are documented here: https://dlang.org/spec/template.html#implicit_template_properties This specific behavior is documented in the first paragraph below that heading, which reads as follows:
 If a template contains members whose name is the same as the 
 template identifier and if the type or the parameters type of 
 these members include at least all the template parameters 
 then these members are assumed to be referred to in a template 
 instantiation
Yes, the behavior is described but not that it is in fact the _only_ way that an eponymous template can in fact be instantiated. I think this behavior will be surprising for someone new to D. For eponymous templates you actually have the short-hand declarative style as well... void foo(S, T)(S s, T t) {} ..ok, needs to call with... foo!(int, int) (1, 2); ...or even just... foo(1, 2); ...and your template parameters will be deduced. This is at least kind-of intuitive. But now if you declare the template in the fully standard way: template foo(S, T) { void foo(S s, T t) {} } ..and you call it in the completely standard way (as you would have in fact been required if template identifier and member were differently named)... foo!(int, int).foo(1, 2); ..it not only does _not_ compile, but gives you an error: onlineapp.d(12): Error: function onlineapp.foo!(int, int).foo(int s, int t) is not callable using argument types () ...where foo!(int, int).foo(int s, int t) is clearly a match! I really like the eponymous template trick and all that, but this did catch me by surprise -I did not realize that the eponymous template style in fact invalidates the 'classic' template invocation style: it is one or the other. I was somehow under the mistaken impression that you could still revert to the classic style, even if your template identifier and member identifier were identical. Is there a technical reason for this limitation? Why are the 'classical' invocation style not allowed for eponymous templates as well? It seems somewhat arbitrary - note that inner/outer functions does not have this limitation - the fllowing is legal and compiles (and does not lead to infinite recursion): int foo(int a, int b) { int foo(int x, int y) {return x+y;} return foo(a, b); } void main() { int z = foo(2, 4); }
Jan 30 2020
next sibling parent reply MoonlightSentinel <moonlightsentinel disroot.org> writes:
On Thursday, 30 January 2020 at 15:14:43 UTC, ShadoLight wrote:
 Is there a technical reason for this limitation? Why are the 
 'classical' invocation style not allowed for eponymous 
 templates as well?

 It seems somewhat arbitrary - note that inner/outer functions 
 does not have this limitation - the fllowing is legal and 
 compiles (and does not lead to infinite recursion):
I guess the intention is to e.g. allow UFCS on the return values of templated functions without ambiguities, e.g.: void main() { import std.algorithm, std.stdio; int[] values = [ 1, 2, 3, 4 ]; values.filter!(i => i % 2 == 0) .map!(i => i / 2) // Does not refer to any hidden member of template filter .each!writeln; } From my POV is void foo(T)() { ... } just a shorthand notation for template foo(T) { void foo() {} } allthough it could probably use some improvements to the documentation.
Jan 30 2020
parent reply ShadoLight <ettienne.gilbert gmail.com> writes:
On Thursday, 30 January 2020 at 16:16:48 UTC, MoonlightSentinel 
wrote:
 On Thursday, 30 January 2020 at 15:14:43 UTC, ShadoLight wrote:
[...]
 From my POV is

 void foo(T)() { ... }

 just a shorthand notation for...
Agreed. My question though is should the 'shorthand' notation _replace_ the 'longhand' notation, or be available _in addition_ to the 'longhand' notation in the eponymous case (so the eponymous notation is just 'syntax sugar' if you will). If you had... template foo(T) { bar(){..} } ...you have no choice but to use foo!(int).bar()- (where T is 'int'). So, I'm asking, in the eponymous case, should... template foo(T) { foo(){..} } force you to use foo!(int)() instead (as is currently the case), or should foo!(int).foo() also still be acceptable/available? For consistency's sake I think it should be but, if there is some reason why this is not technically possible/advisable, I was hoping someone would enlighten me. And, in that case some of the examples in the documentation needs fixing.
Jan 30 2020
parent reply MoonlightSentinel <moonlightsentinel disroot.org> writes:
On Thursday, 30 January 2020 at 17:00:08 UTC, ShadoLight wrote:
 Agreed. My question though is should the 'shorthand' notation 
 _replace_ the 'longhand' notation, or be available _in 
 addition_ to the 'longhand' notation in the eponymous case (so 
 the eponymous notation is just 'syntax sugar' if you will).
Consider the following example: T foo(T = int)(T val = T.init) { return val + 1; } void main() { auto i = foo!().foo(); writeln(i); } What should be the value of i? 1: foo!() is the template instantiation, foo() the method call 2: foo!() and foo() are method calls. The answer is 2 (because empty braces are optional) but with your proposed change it would be ambigous (unless one interpreation was prioritized which would probably be more confusing). Maybe we should rather ask what benefit results from allowing this explicit notation? IMHO the entire purpose of epynemous templates is to make templates containing one public* symbol less verbose which is a common use case. *public ~ useful outside of the templated symbol
 And, in that case some of the examples in the documentation 
 needs fixing.
Agreed, the documentation could use some polishing.
Jan 30 2020
parent ShadoLight <ettienne.gilbert gmail.com> writes:
On Thursday, 30 January 2020 at 20:00:05 UTC, MoonlightSentinel 
wrote:
 On Thursday, 30 January 2020 at 17:00:08 UTC, ShadoLight wrote:
[..]
 ...your proposed change it would be ambigous ...
Ok, that makes sense - I did not consider the impact of the optional empty braces. Thank you.
Jan 30 2020
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Thursday, 30 January 2020 at 15:14:43 UTC, ShadoLight wrote:
 Is there a technical reason for this limitation? Why are the 
 'classical' invocation style not allowed for eponymous 
 templates as well?
The 'classical' invocation style is not allowed because it would make the language grammatically ambiguous. Without semantic analysis, the compiler would have no way of knowing whether `foo!(int, int)` was intended to refer to the template instance foo!(int, int), or the eponymous member foo!(int, int).foo.
Jan 30 2020
parent ShadoLight <ettienne.gilbert gmail.com> writes:
On Thursday, 30 January 2020 at 20:42:02 UTC, Paul Backus wrote:
 On Thursday, 30 January 2020 at 15:14:43 UTC, ShadoLight wrote:

[..]
 would make the language grammatically ambiguous...
OK, understood. Thank you.
Jan 30 2020
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/30/20 9:10 AM, ShadoLight wrote:

 Why does the 'classical' template calling convention not work anymore in 
 this case? (if the template name and function name are different it 
 obviously still works). Note the templates were not defined in the 
 simplified 'Eponymous Trick' style i.e.:
 
I know your question is pretty much answered, but to give you some historical perspective, the long form WAS allowed in the past (it may still be that way in D1), and I think there were ambiguity problems as described. So you can no longer do that. It was actually much more restrictive before -- e.g. in order to do an eponymous template, you could have no other members in the template. Probably we should adjust the examples to remove those that no longer work. -Steve
Jan 31 2020
parent reply ShadoLight <ettienne.gilbert gmail.com> writes:
On Friday, 31 January 2020 at 15:37:06 UTC, Steven Schveighoffer 
wrote:
 On 1/30/20 9:10 AM, ShadoLight wrote:
 but to give you some historical perspective...
[..]
 It was actually much more restrictive before -- e.g.  in order 
 to do an eponymous template, you could have no other members in 
 the template.
Thanks for the historical perspective Steve. Appreciated. When you refer to 'other members' in the eponymous template, I notice that they are indeed allowed, but are effectively hidden (if they have a different name from the template name) inside the eponymous template, correct? For example, this works fine in 'classical' template style - all members are 'public' and accessible: template bar(T) { T z; //Allowed... void b1(T x){z+=x;} //Allowed... void b2(T x){z-=x;} //Allowed... } void main() { bar!(int).b1(4); //Works.. writeln(bar!int.z); //Works.. bar!(int).b2(5); //Works.. writeln(bar!int.z); //Works.. } For eponymous templates, only members with the overloaded template identifier are accessible 'from the outside'. template foo(T) { T z; //Allowed... void foo(T x){f1(x);} //Allowed... T foo(){return z;} //Allowed... void f1(T x){z+=x;} //Allowed... } void main() { foo(3); //Works.. foo(4); //Works.. writeln(foo!int.z); //onlineapp.d(21): Error: no property z for type int foo!int.f1(3); //onlineapp.d(21): Error: no property f1 for type int writeln(foo!int()); //Works.. } So the eponymous template invocation has to use the eponymous 'trick' and only overloaded members are accessible. Not bad and definitely an improvement , but I still find the inconsistency jarring... IIUC this 'ambiguity' would have been avoidable if template argument braces were not optional, right? I do know this horse has bolted, but personally I think it D made a mistake to make the braces on compile-/run-time arguments on template/function invocation optional in the zero/one argument case. It is not even completely symmetric between compile- and run-time: in compile time you can leave the braces off in the case of zero and one arguments, but for run-time only for zero arguments. For want of the effort to type 2 braces, a fair bit of complexity and some inconsistency has been introduced into the language. And I remember that it actually made code harder to read when I started out in D, not easier. I support the dropping of the braces in the case of property functions, but that use case is quite clear-cut and I think easy to handle based on the property annotation.
Feb 02 2020
parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 2 February 2020 at 13:01:26 UTC, ShadoLight wrote:
 Not bad and definitely an improvement , but I still find the 
 inconsistency jarring... IIUC this 'ambiguity' would have been 
 avoidable if template argument braces were not optional, right?
No, it would still be ambiguous: struct S(T) {} alias a = S!(int); // Should this assertion pass or fail? static assert(is(a));
Feb 02 2020
parent reply ShadoLight <ettienne.gilbert gmail.com> writes:
On Sunday, 2 February 2020 at 16:23:42 UTC, Paul Backus wrote:

[..]
 No, it would still be ambiguous:

 struct S(T) {}

 alias a = S!(int);

 // Should this assertion pass or fail?
 static assert(is(a));
Sorry, I don't get it. AFAICS 'is(a)' should return true (since a is an alias for a full type here) - and braces being compulsory or optional does not affect this. AFAICS: struct S(T) {} alias a = S!(int); alias b = S; // Should this assertion pass or fail? static assert(is(a)); //PASS static assert(is(b)); //FAIL But I don't see how braces will affect this. Can you explain?
Feb 02 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 2/2/20 12:51 PM, ShadoLight wrote:

 // Should this assertion pass or fail?
 static assert(is(a));  //PASS
 static assert(is(b));  //FAIL
 
 But I don't see how braces will affect this. Can you explain?
First, note that: struct S(T) {} is *exactly* equivalent to (in fact, you can write it this way, and it works exactly the same): template S(T) { struct S {} } So does S!int refer to the template instantiation (which is not a type) or the eponymous S struct inside the template (which is a type)? This was Paul's point. Since you didn't use any members, there is an ambiguity of intention (if accessing other parts of the template are allowed). Even if you use a member, it's not hard to come up with an ambiguous example: struct S(T) { template S(X) {} } What does S!int.S refer to? The S template inside the S struct, or the S struct itself inside the outer S template? This is the main reason why eponymous templates were disallowed from using the classical template access syntax. I think this was not a mistake, and we are better off for it. -Steve
Feb 02 2020
parent reply ShadoLight <ettienne.gilbert gmail.com> writes:
On Sunday, 2 February 2020 at 18:30:17 UTC, Steven Schveighoffer 
wrote:

Thanks for taking the time to explain. However, when I tested my 
results does not seem to match your explanation.

 First, note that:

 struct S(T) {}

 is *exactly* equivalent to (in fact, you can write it this way, 
 and it works exactly the same):

 template S(T) { struct S {} }
Yes, agreed.
 So does S!int refer to the template instantiation (which is not 
 a type) or the eponymous S struct inside the template (which is 
 a type)? This was Paul's point.
I get what you are trying to say, but testing actually shows S!int is already a type in the eponymous case (but not in the classical case). If I do as in Paul's example... template S(T) { struct S {T x;} } // Should this assertion pass or fail? static assert(is(S!(int))); //PASS void main() { auto a = S!(int)(3); writeln(typeof(a).stringof); } ... it actually compiles and the assertion passes (and prints S!int as the type), so it seems the eponymous template instantiation already created its eponymous struct as well, no? But I guess that in terms of 'short-hand' syntax it actually makes sense since it is an eponymous template and, as you/Paul explained previously, it should only be invoked in this way. In contrast, for the non-eponymous case.... template R(T) { struct Q {T x;} } // Should this assertion pass or fail? //static assert(is(R!(int))); //FAIL void main() { auto b = R!(int).Q(3); writeln(typeof(b).stringof); } ... now the assertion fails as expected, but the type printed is simply Q. So we actually have this: template S(T) { struct S {} } template R(T) { struct Q {} } static assert(is(S!(int)) == true); //PASS static assert(is(R!(int)) == false); //PASS I can understand (as MoonlightSentinel's example showed), that optional braces can make this ambiguous and that is the current situation. But, my question was if this was avoidable if braces were not optional. Paul's answer was that non-optional braces will not make... alias a = S!(int); ... non-ambiguous, but I still don't get that based on the above results.
Feb 02 2020
parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 2 February 2020 at 23:39:10 UTC, ShadoLight wrote:
 But, my question was if this was avoidable if braces were not 
 optional. Paul's answer was that non-optional braces will not 
 make...
     alias a = S!(int);
 ... non-ambiguous, but I still don't get that based on the 
 above results.
The results you've shown are based on the currently-implemented behavior, which is indeed unambiguous: S!(int) always refers to the eponymous struct, not the template instance that contains it. This is true whether or not you use braces. If the behavior were changed as you have previously proposed, so that `S!(int)` could refer *either* to the eponymous struct *or* the template instance, then the alias declaration would become ambiguous. Again, this would be true whether or not you used braces.
Feb 02 2020
parent ShadoLight <ettienne.gilbert gmail.com> writes:
On Sunday, 2 February 2020 at 23:48:45 UTC, Paul Backus wrote:
 On Sunday, 2 February 2020 at 23:39:10 UTC, ShadoLight wrote:
 But, my question was if this was avoidable if braces were not 
 optional. Paul's answer was that non-optional braces will not 
 make...
     alias a = S!(int);
 ... non-ambiguous, but I still don't get that based on the 
 above results.
[..]
 If the behavior were changed as you have previously proposed, 
 so that `S!(int)` could refer *either* to the eponymous struct 
 *or* the template instance, then the alias declaration would 
 become ambiguous. Again, this would be true whether or not you 
 used braces.
Ok, I get it. Thanks.
Feb 02 2020