www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - typeof(func!0) != typeof(func!0())

reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
I have this simple code:
```d
struct U
{
     auto ref func(int i)() { return this; }
}

void main()
{
     {
         alias type = typeof(U().func!0);
         pragma(msg, type);  		// pure nothrow  nogc ref  safe U() 
return
         pragma(msg, is(type : U));  // false

         auto u = U().func!0;
         pragma(msg, typeof(u));		// U
     }
     {
         alias type = typeof(U().func!0());
         pragma(msg, type);  		// U
         pragma(msg, is(type : U));  // true

         auto u = U().func!0();
         pragma(msg, typeof(u));		// U
     }
}
```
Why does `typeof(U().func!0)` != `U`? How can I check that the 
returned value has type `U` in this case?
Aug 21 2022
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 8/21/22 21:39, Andrey Zherikov wrote:

          alias type = typeof(U().func!0);
          pragma(msg, type);          // pure nothrow  nogc ref  safe U()
 return
This is where the property keyword makes a difference: property auto ref func(int i)() { return this; } Now U().func!0 will be a call in your expression. But property is not recommended (deprecated?). But I think std.traits.ReturnType is more explicit and does work in this case: import std.traits; alias type = ReturnType!(U().func!0); Ali
Aug 21 2022
parent Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Monday, 22 August 2022 at 05:25:50 UTC, Ali Çehreli wrote:
 This is where the  property keyword makes a difference:

      property auto ref func(int i)() { return this; }

 Now U().func!0 will be a call in your expression.
Is original `U().func!0` not a call?
 But I think std.traits.ReturnType is more explicit and does 
 work in this case:

         import std.traits;
         alias type = ReturnType!(U().func!0);
This works but looks strange - I'm checking the type of UDA expression: ```d (U().func!0) int b; pragma(msg, __traits(getAttributes, b)); // tuple(U().func) pragma(msg, typeof(__traits(getAttributes, b)[0])); // pure nothrow nogc ref safe U() return pragma(msg, ReturnType!(__traits(getAttributes, b)[0])); // U pragma(msg, is(typeof(__traits(getAttributes, b)[0]) : U)); // false pragma(msg, hasUDA!(b, U)); // false ```
Aug 22 2022
prev sibling parent reply JG <someone simewhere.com> writes:
On Monday, 22 August 2022 at 04:39:18 UTC, Andrey Zherikov wrote:
 I have this simple code:
 ```d
 struct U
 {
     auto ref func(int i)() { return this; }
 }

 void main()
 {
     {
         alias type = typeof(U().func!0);
         pragma(msg, type);  		// pure nothrow  nogc ref  safe 
 U() return
         pragma(msg, is(type : U));  // false

         auto u = U().func!0;
         pragma(msg, typeof(u));		// U
     }
     {
         alias type = typeof(U().func!0());
         pragma(msg, type);  		// U
         pragma(msg, is(type : U));  // true

         auto u = U().func!0();
         pragma(msg, typeof(u));		// U
     }
 }
 ```
 Why does `typeof(U().func!0)` != `U`? How can I check that the 
 returned value has type `U` in this case?
Why not just change to: alias type = typeof(U().func!0());
Aug 21 2022
parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Monday, 22 August 2022 at 06:01:11 UTC, JG wrote:
 Why not just change to:
 alias type = typeof(U().func!0());
This is user's code and `U().func!0` is legit syntax.
Aug 22 2022
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 22 August 2022 at 11:24:59 UTC, Andrey Zherikov wrote:
 On Monday, 22 August 2022 at 06:01:11 UTC, JG wrote:
 Why not just change to:
 alias type = typeof(U().func!0());
This is user's code and `U().func!0` is legit syntax.
Workaround: wrap it in a lambda. ```d import std.traits; alias type = ReturnType!(() => U().func!0); ```
Aug 22 2022
next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 8/22/22 8:04 AM, Paul Backus wrote:
 On Monday, 22 August 2022 at 11:24:59 UTC, Andrey Zherikov wrote:
 On Monday, 22 August 2022 at 06:01:11 UTC, JG wrote:
 Why not just change to:
 alias type = typeof(U().func!0());
This is user's code and `U().func!0` is legit syntax.
Workaround: wrap it in a lambda. ```d import std.traits; alias type = ReturnType!(() => U().func!0); ```
Or without having to import phobos: ```d alias type = typeof((() => U().func!0)()); ``` I actually created a small utility for this in my code: ```d auto ref eval(T)(auto ref T expr) { return expr; } ``` so now I can just do: ```d alias type = typeof(eval(U().func!0))); ``` -Steve
Aug 22 2022
prev sibling parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Monday, 22 August 2022 at 12:04:09 UTC, Paul Backus wrote:
 On Monday, 22 August 2022 at 11:24:59 UTC, Andrey Zherikov 
 wrote:
 On Monday, 22 August 2022 at 06:01:11 UTC, JG wrote:
 Why not just change to:
 alias type = typeof(U().func!0());
This is user's code and `U().func!0` is legit syntax.
Workaround: wrap it in a lambda. ```d import std.traits; alias type = ReturnType!(() => U().func!0); ```
My situation is that user can write some UDA expression and I'm checking whether it's of a type `U` using `hasUDA!(sym, U)` and `getUDAs!(sym, U)`. Is the users uses `U()` or `U().func!0()`, everything works. But `U().func!0` does not because its type is not `U`. So to handle this use case it seems I need to implement my own `hasUDA` and `getUDAs` based on `ReturnType`.
Aug 22 2022
next sibling parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
But the question is still opened: why is `typeof(U().func!0)` not 
the same as `typeof(U().func!0())`?

There is also inconsistency within UFCS - type depends on whether 
function is a member or standalone (even if I replace `auto ref` 
with `ref U`):
```d
struct U
{
     ref U func(int i)() { return this; }
}
ref U func2(int i)(ref U u) { return u; }

void main()
{
     U u;
     pragma(msg, typeof(u.func!0));	// pure nothrow  nogc ref 
 safe U() return
     pragma(msg, typeof(u.func2!0));	// U
}
```
Aug 22 2022
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 22 August 2022 at 14:43:24 UTC, Andrey Zherikov wrote:
 But the question is still opened: why is `typeof(U().func!0)` 
 not the same as `typeof(U().func!0())`?
Probably because if it were the same, it would be completely impossible to introspect on the type of `U.func!0` directly. The closest you could get would be to examine `typeof(&U.func!0)`; i.e., the type of the function pointer rather than the function itself. I'm not totally convinced that the current behavior is the correct decision here, but there is a real tradeoff.
Aug 22 2022
parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Monday, 22 August 2022 at 15:20:46 UTC, Paul Backus wrote:
 On Monday, 22 August 2022 at 14:43:24 UTC, Andrey Zherikov 
 wrote:
 But the question is still opened: why is `typeof(U().func!0)` 
 not the same as `typeof(U().func!0())`?
Probably because if it were the same, it would be completely impossible to introspect on the type of `U.func!0` directly. The closest you could get would be to examine `typeof(&U.func!0)`; i.e., the type of the function pointer rather than the function itself. I'm not totally convinced that the current behavior is the correct decision here, but there is a real tradeoff.
I have an impression that template function can be called without parenthesis if it doesn't have run-time parameters so `func!0` is the same as `func!0()`. Am I wrong? If we consider free function vs. member function, why is `typeof(u.func!0)` not the same as `typeof(u.func2!0)` here? ```d struct U { ref U func(int i)() { return this; } } ref U func2(int i)(ref U u) { return u; } void main() { U u; pragma(msg, typeof(u.func!0)); // pure nothrow nogc ref safe U() return pragma(msg, typeof(u.func2!0)); // U } ``` Is `typeof(u.func2!0)` correct in this case? If so then I'll just convert my API to free-standing functions and everything will work as a magic (without my own implementation of `getUDA`).
Aug 22 2022
next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 8/22/22 12:19 PM, Andrey Zherikov wrote:
 On Monday, 22 August 2022 at 15:20:46 UTC, Paul Backus wrote:
 On Monday, 22 August 2022 at 14:43:24 UTC, Andrey Zherikov wrote:
 But the question is still opened: why is `typeof(U().func!0)` not the 
 same as `typeof(U().func!0())`?
Probably because if it were the same, it would be completely impossible to introspect on the type of `U.func!0` directly. The closest you could get would be to examine `typeof(&U.func!0)`; i.e., the type of the function pointer rather than the function itself. I'm not totally convinced that the current behavior is the correct decision here, but there is a real tradeoff.
I have an impression that template function can be called without parenthesis if it doesn't have run-time parameters so `func!0` is the same as `func!0()`. Am I wrong? If we consider free function vs. member function, why is `typeof(u.func!0)` not the same as `typeof(u.func2!0)` here? ```d struct U {     ref U func(int i)() { return this; } } ref U func2(int i)(ref U u) { return u; } void main() {     U u;     pragma(msg, typeof(u.func!0));    // pure nothrow nogc ref safe U() return     pragma(msg, typeof(u.func2!0));    // U } ``` Is `typeof(u.func2!0)` correct in this case? If so then I'll just convert my API to free-standing functions and everything will work as a magic (without my own implementation of `getUDA`).
It's not magic. It's UFCS. in the case of the struct function, there is an ambiguity. Do you mean the function named `func` in the *namespace* of `u` (which is actually `U`)? Or do you mean to call `func` *using* `u`? The D compiler opts for the former. However, with `func2`, there is no `func2` in u's namespace. So the only option is an actual call. -Steve
Aug 22 2022
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 22 August 2022 at 16:19:06 UTC, Andrey Zherikov wrote:
 I have an impression that template function can be called 
 without parenthesis if it doesn't have run-time parameters so 
 `func!0` is the same as `func!0()`. Am I wrong?
You're not wrong. This behavior is a special case in `typeof`. I was merely attempting to explain why such a special case might have been introduced.
 If we consider free function vs. member function, why is 
 `typeof(u.func!0)` not the same as `typeof(u.func2!0)` here?
My guess is that `u.func2!0` is rewritten to `func2!0(u)` during semantic analysis, so it does not trigger the special-case behavior.
 Is `typeof(u.func2!0)` correct in this case? If so then I'll 
 just convert my API to free-standing functions and everything 
 will work as a magic (without my own implementation of 
 `getUDA`).
It's probably not worth completely changing your API design just to work around this issue. Also, even if you do this, it is still possible for a user to run into a same problem with a member function of one of their own types; for example: ```d import your.library; struct MyStruct { U myFunc() { /* ... */ } } (MyStruct.myFunc) whatever; ```
Aug 22 2022
parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Monday, 22 August 2022 at 16:42:50 UTC, Paul Backus wrote:
 It's probably not worth completely changing your API design 
 just to work around this issue. Also, even if you do this, it 
 is still possible for a user to run into a same problem with a 
 member function of one of their own types; for example:

 ```d
 import your.library;

 struct MyStruct {
     U myFunc() { /* ... */ }
 }

  (MyStruct.myFunc) whatever;
 ```
This will be users' issue then, not mine :) I'm providing a struct (`U` in examples above) with some API and I'm looking for this struct as an UDA (through `hasUDA`/`getUDAs`). And actually I already have a mix of member-functions and free-functions in my API so it's gonna be more consistent if I make all of them free-functions.
Aug 22 2022
parent Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Monday, 22 August 2022 at 17:40:59 UTC, Andrey Zherikov wrote:
 I'm providing a struct (`U` in examples above) with some API 
 and I'm looking for this struct as an UDA (through 
 `hasUDA`/`getUDAs`). And actually I already have a mix of 
 member-functions and free-functions in my API so it's gonna be 
 more consistent if I make all of them free-functions.
Just checked - free functions work so I'll migrate API to them. Thanks everyone for answering my questions.
Aug 22 2022
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 22 August 2022 at 14:42:10 UTC, Andrey Zherikov wrote:
 My situation is that user can write some UDA expression and I'm 
 checking whether it's of a type `U` using `hasUDA!(sym, U)` and 
 `getUDAs!(sym, U)`. Is the users uses `U()` or `U().func!0()`, 
 everything works. But `U().func!0` does not because its type is 
 not `U`. So to handle this use case it seems I need to 
 implement my own `hasUDA` and `getUDAs` based on
 `ReturnType`.
My first instinct is to say that this is the user's mistake. UDAs are not evaluated like normal expressions, and anyone using UDAs is going to have to learn that sooner or later. That said, if you want to try and present a nicer API, my recommendation would be to accept *either* (a) an instance of `U`, or (b) a callable that returns a `U`, which you can do with code like the following: ```d import std.traits, std.meta; enum isOrReturnsU(alias attr) = is(typeof(attr) == U) || is(typeof(attr()) == U); alias getMyUDAs(alias sym) = Filter!(isOrReturnsU, getUDAs!sym); ``` Or if you want to generalize to arbitrary predicates: ```d alias filterUDAs(alias sym, alias pred) = Filter!(pred, getUDAs!sym); ```
Aug 22 2022
parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Monday, 22 August 2022 at 15:15:22 UTC, Paul Backus wrote:
 My first instinct is to say that this is the user's mistake. 
 UDAs are not evaluated like normal expressions, and anyone 
 using UDAs is going to have to learn that sooner or later.
My feeling was that UDA expression is just an expression that's evaluated at compile time. Am I wrong? Where can I read about the differences between UDA and 'normal' expressions?
 That said, if you want to try and present a nicer API, my 
 recommendation would be to accept *either* (a) an instance of 
 `U`, or (b) a callable that returns a `U`, which you can do 
 with code like the following:

 ```d
 import std.traits, std.meta;

 enum isOrReturnsU(alias attr) = is(typeof(attr) == U) || 
 is(typeof(attr()) == U);
 alias getMyUDAs(alias sym) = Filter!(isOrReturnsU, getUDAs!sym);
 ```

 Or if you want to generalize to arbitrary predicates:

 ```d
 alias filterUDAs(alias sym, alias pred) = Filter!(pred, 
 getUDAs!sym);
 ```
Thanks for this suggestion, I'll try it out!
Aug 22 2022
parent Paul Backus <snarwin gmail.com> writes:
On Monday, 22 August 2022 at 16:06:37 UTC, Andrey Zherikov wrote:
 On Monday, 22 August 2022 at 15:15:22 UTC, Paul Backus wrote:
 My first instinct is to say that this is the user's mistake. 
 UDAs are not evaluated like normal expressions, and anyone 
 using UDAs is going to have to learn that sooner or later.
My feeling was that UDA expression is just an expression that's evaluated at compile time. Am I wrong? Where can I read about the differences between UDA and 'normal' expressions?
A UDA can be either an expression or a symbol. Here are some examples of symbol UDAs that are not valid expressions: ```d import std.stdio; struct S; template t() {} S // a type (std.stdio) // a module t // a template int n; ```
Aug 22 2022