digitalmars.D.learn - typeof(func!0) != typeof(func!0())
- Andrey Zherikov (27/27) Aug 21 2022 I have this simple code:
- =?UTF-8?Q?Ali_=c3=87ehreli?= (10/13) Aug 21 2022 This is where the @property keyword makes a difference:
- Andrey Zherikov (15/22) Aug 22 2022 This works but looks strange - I'm checking the type of UDA
- JG (3/30) Aug 21 2022 Why not just change to:
- Andrey Zherikov (2/4) Aug 22 2022 This is user's code and `U().func!0` is legit syntax.
- Paul Backus (6/10) Aug 22 2022 Workaround: wrap it in a lambda.
- Steven Schveighoffer (14/29) Aug 22 2022 Or without having to import phobos:
- Andrey Zherikov (8/20) Aug 22 2022 My situation is that user can write some UDA expression and I'm
- Andrey Zherikov (19/19) Aug 22 2022 But the question is still opened: why is `typeof(U().func!0)` not
- Paul Backus (8/10) Aug 22 2022 Probably because if it were the same, it would be completely
- Andrey Zherikov (23/34) Aug 22 2022 I have an impression that template function can be called without
- Steven Schveighoffer (9/48) Aug 22 2022 It's not magic. It's UFCS.
- Paul Backus (18/27) Aug 22 2022 You're not wrong. This behavior is a special case in `typeof`. I
- Andrey Zherikov (7/18) Aug 22 2022 This will be users' issue then, not mine :)
- Andrey Zherikov (3/8) Aug 22 2022 Just checked - free functions work so I'll migrate API to them.
- Paul Backus (19/26) Aug 22 2022 My first instinct is to say that this is the user's mistake. UDAs
- Andrey Zherikov (5/23) Aug 22 2022 My feeling was that UDA expression is just an expression that's
- Paul Backus (12/19) Aug 22 2022 A UDA can be either an expression or a symbol. Here are some
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
On 8/21/22 21:39, Andrey Zherikov wrote:alias type = typeof(U().func!0); pragma(msg, type); // pure nothrow nogc ref safe U() returnThis 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
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
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
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
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:Workaround: wrap it in a lambda. ```d import std.traits; alias type = ReturnType!(() => U().func!0); ```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
On 8/22/22 8:04 AM, Paul Backus wrote:On Monday, 22 August 2022 at 11:24:59 UTC, Andrey Zherikov wrote: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))); ``` -SteveOn Monday, 22 August 2022 at 06:01:11 UTC, JG wrote:Workaround: wrap it in a lambda. ```d import std.traits; alias type = ReturnType!(() => U().func!0); ```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
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: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`.On Monday, 22 August 2022 at 06:01:11 UTC, JG wrote:Workaround: wrap it in a lambda. ```d import std.traits; alias type = ReturnType!(() => U().func!0); ```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
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
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
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: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`).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
On 8/22/22 12:19 PM, Andrey Zherikov wrote:On Monday, 22 August 2022 at 15:20:46 UTC, Paul Backus wrote: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. -SteveOn Monday, 22 August 2022 at 14:43:24 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? 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`).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
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
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
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
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
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
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: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; ```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?
Aug 22 2022