Thread overview
enum functions
Jan 08, 2023
Salih Dincer
Jan 08, 2023
Salih Dincer
Jan 10, 2023
Salih Dincer
Jan 10, 2023
Adam D Ruppe
Jan 11, 2023
bauss
Jan 11, 2023
Salih Dincer
Jan 11, 2023
Ali Çehreli
January 08, 2023

Aho,

I accidentally had a function like this:

    enum oneHalf(T)(T i) {
        return i >> 1;
    }

I'm wondering 2 things; firstly, does having an enum mean there is no auto-return? Or could it be CTFE?

Second, why is there no inference when I use an enum in one of the functions below?

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

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

import std;

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

Thanks...

SDB@79

January 08, 2023

On Sunday, 8 January 2023 at 18:42:58 UTC, Salih Dincer wrote:

>

I accidentally had a function like this:

    enum oneHalf(T)(T i) {
        return i >> 1;
    }

Also it seemed odd to me that this could compile!

SDB@79

January 10, 2023

On Sunday, 8 January 2023 at 18:42:58 UTC, Salih Dincer wrote:

>

I'm wondering 2 things; firstly, does having an enum mean there is no auto-return? Or could it be CTFE?

Second, why is there no inference when I use an enum in one of the functions below?

I'm still wondering 😀

SDB@79

January 10, 2023
On Sunday, 8 January 2023 at 18:42:58 UTC, Salih Dincer wrote:
> I'm wondering 2 things; firstly, does having an enum mean there is no auto-return? Or could it be CTFE?

It means nothing. The keyword tells the parser a function is about to begin, which triggers return type inference (exactly the same as `auto`), but otherwise it is completely ignored.
January 11, 2023
On Tuesday, 10 January 2023 at 12:55:34 UTC, Adam D Ruppe wrote:
> On Sunday, 8 January 2023 at 18:42:58 UTC, Salih Dincer wrote:
>> I'm wondering 2 things; firstly, does having an enum mean there is no auto-return? Or could it be CTFE?
>
> It means nothing. The keyword tells the parser a function is about to begin, which triggers return type inference (exactly the same as `auto`), but otherwise it is completely ignored.

I'm curious about why it is allowed and whether we somehow could use it for something meaningful since it really just is an alias for auto right now.

Perhaps it could be used to create functions that only live at compile-time.
January 11, 2023

On 1/10/23 7:55 AM, Adam D Ruppe wrote:

>

On Sunday, 8 January 2023 at 18:42:58 UTC, Salih Dincer wrote:

>

I'm wondering 2 things; firstly, does having an enum mean there is no auto-return? Or could it be CTFE?

It means nothing. The keyword tells the parser a function is about to begin, which triggers return type inference (exactly the same as auto), but otherwise it is completely ignored.

Mostly correct. enum is a storage class, which means a declaration is about to begin. Once the declaration is decided as a function definition, type inference is performed, and the storage class is checked to see how it affects the declaration. In some cases, there is an effect, in some there is not.

static foo() {} // static does nothing here
final bar() {} // final does nothing here
enum baz() {} // enum does nothing here
__gshared fun() {} // __gshared does nothing here

struct S
{
   // return type is int *, `this` (unused) is const
   const foo() { return new int; }
   // same for immutable, shared, inout
}

Aside from the const/immutable/shared/inout storage classes (which do affect semantics, but maybe in slightly confusing ways) there are some which are pretty obvious and also work with type inference:

  1. deprecated
  2. extern
  3. abstract
  4. override
  5. synchronized
  6. auto
  7. scope
  8. nothrow
  9. pure
  10. ref

-Steve

January 11, 2023

On Tuesday, 10 January 2023 at 12:55:34 UTC, Adam D Ruppe wrote:

>

It means nothing. The keyword tells the parser a function is about to begin, which triggers return type inference (exactly the same as auto), but otherwise it is completely ignored.

Sorry to give compelling examples but it can be pretty confusing if it comes across as if by mistake, am I wrong?

void main()
{
    enum foo : char { a = 'H', b = 'i' }
    enum bar() { return new foo; }

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

    writeln; // Hi
}

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.

Thanks...

SDN@79

January 11, 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