Jump to page: 1 2
Thread overview
Template Usage with Eponymous Trick
Jan 30, 2020
ShadoLight
Jan 30, 2020
ShadoLight
Jan 30, 2020
Paul Backus
Jan 30, 2020
ShadoLight
Jan 30, 2020
MoonlightSentinel
Jan 30, 2020
ShadoLight
Jan 30, 2020
MoonlightSentinel
Jan 30, 2020
ShadoLight
Jan 30, 2020
Paul Backus
Jan 30, 2020
ShadoLight
Feb 02, 2020
ShadoLight
Feb 02, 2020
Paul Backus
Feb 02, 2020
ShadoLight
Feb 02, 2020
ShadoLight
Feb 02, 2020
Paul Backus
Feb 03, 2020
ShadoLight
January 30, 2020
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 ()
onlineapp.d(23):        missing argument for parameter #1: int _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
January 30, 2020
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
January 30, 2020
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
January 30, 2020
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);
}

January 30, 2020
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.
January 30, 2020
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.

January 30, 2020
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.
January 30, 2020
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.
January 30, 2020
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.

January 30, 2020
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.


« First   ‹ Prev
1 2