enum functions

Ali Çehreli acehreli at yahoo.com
Wed Jan 11 19:23:16 UTC 2023


vvvvvvvvvvvv TLDR vvvvvvvvvvvv

Eponymous templates that define a function pointer does not transfer the 
function call parameter to the enclosing template for type deduction. 
(Note: The term "deduction" is used with template parameters, not 
"inference".)

Also note that I am replacing 'enum' with 'auto' below to show this is 
not related to 'enum' vs 'auto'.

template foo (T) {
     auto foo = (T i) => i >> 1;
}

void main() {
     foo(42); // <-- Compilation ERROR
}

Error: none of the overloads of template `deneme.foo` are callable using 
argument types `!()(int)`
        Candidate is: `foo(T)`

I am not sure whether this limitation is a bug.

^^^^^^^^^^^^ TLDR ^^^^^^^^^^^^

On 1/11/23 10:01, Salih Dincer wrote:

 > void main()
 > {
 >      enum foo : char { a = 'H', b = 'i' }

That defines two values: foo.a is 'H' and foo.b is 'i'.

 >      enum bar() { return new foo; }

That is a function that returns a dynamically allocated foo value. Since 
'new' makes pointers for value types like int and enum, I think the 
return type of 'bar' is foo*.

And that pointer is pointing at a foo.init, which happens to be the 
first value defined: 'H'.

 >
 >      import std.stdio;
 >      foreach(char f; [bar.a, bar.b])

'bar' is a function call that returned a pointer. Hm. Finally I see what 
you are talking about.

Normally, one might have written (*bar) to get the value. Let's try by 
using the following array:

   [(*bar), (*bar)]

Ok, it's what I expect:

HH

Now the interesting part is what does (*bar).a and (*bar).b mean? Let's try:

   [(*bar).a, (*bar).b]

Ok, it gives

Hi

The interesting thing is, foo.a does not print 'H' but 'a':

     writeln(foo.a);
     writeln(foo.b);

a
b

Ah! Finally I see your explicit 'char' in the foreach loop. That is what 
makes your code print 'H' and 'i'. :)

 >          f.write;
 >
 >      writeln; // Hi
 > }

I've just learned something: You can reach different enum values 
directly from an value itself:

import std;

void main()
{
     // A type:
     enum foo : char { a = 'H', b = 'i' }

     // A variable:
     enum x = foo.a;

     // Really?
     writeln(x.b);
}

Ok, it kind of makes sense but I would have written the following e.g. 
in template code. (?)

     writeln(typeof(x).b);

 > I think this use of enums should be prohibited. So, can I get an answer
 > about not being able to make a type inference? So I'm very curious about
 > the second part of the my post.

Before looking at your example, here is what I tested. The code proves 
that type inference is the same for 'auto' and 'enum' functions:

auto foo(T)(T t) {
     return t;
}

enum bar(T)(T t) {
     return t;
}

struct S {}

import std.meta;

void main() {
     alias functions = AliasSeq!(foo, bar);
     alias types = AliasSeq!(int, double, S, string, const(float));

     static foreach (type; types) {
         pragma(msg, "Testing ", type);
         static foreach (func; functions) {
             static assert (is (typeof(func(type.init)) == type));
         }
     }
}

It uses two function templates and a few types and as expected, the 
return types of the functions are the same.

Ok, back to your code:

 > template foo (T) {
 >     enum foo = (T i) => i >> 1;
 > }

That local 'foo' is a literal value of an anonymous function. (The local 
'foo' is not a template.) Reminding myself: Literals are rvalues and 
they are not variables. For example, they don't have addresses.

 > template bar (T) {
 >     auto bar (T i) => i >> 1;
 > }

That's different: There was an equals sign after 'foo' but not here. So 
your example is different from e.g. the following two definitions:

auto x() {}
enum y() {}

If you are curious about this case, as Adam said, there is no difference 
between 'auto' and 'enum'.
But again, your code has that extra '=' character for 'foo' and that 
makes a difference.

Getting back to 'bar', so that local 'bar' is a function definition. 
Now, calling bar from the outside as bar(42) is the same as writing 
bar!int.bar(42). (This is the eponymous template convenience.)

 > import std;

 > void main()
 > {
 >     assert(oneHalf(42) == 21);
 >     42.foo!int.writeln; // no inference: "21"

Your "no inference" comment was confusing me. Now I see that you meant 
that you can't write the following:

   42.foo.writeln; // <-- Compilation ERROR

Although you can write the following:

 >     42.bar.writeln; // "21"
 > }

Ok, let's look at the problem with the understanding of 'foo' being a 
literal. Yes, I can demonstrate the issue that mix it with the concept 
of type inference:

import std;

void main() {
     enum foo = {           // Note '='
         writeln("foo");
     };

     auto bar() {
         writeln("bar");
     }

     foo;  // <-- Compilation ERROR
     bar;
}

foo line has a compilation error:

Error: `__lambda1` has no effect

Because it's a function pointer, one needs to call it with parenthesis:

     foo();
     bar;

Now it works.

The reason why 'bar' does not need the parenthesis is because functions 
have that famous convenience in D: When the parameter list is empty, the 
parenthesis are optional. Apparently, this is not the case for literals. 
There is a good reason for that: You wouldn't want 'foo' be a function 
call every time.

For example, we might want the function pointer value of 'foo', not the 
result of calling it:

   auto anotherFunctionPointer = foo;

But then you would argue that your 'foo' does have an 'int' parameter. 
Why wouldn't the enclosing template parameter deduced for it? I agree. I 
put this issue at the top as TLDR.

Ali



More information about the Digitalmars-d-learn mailing list