digitalmars.D - New operators opStaticIndex and friends
- Q. Schroll (54/54) May 14 2019 I whish, D has these operators:
- H. S. Teoh (5/17) May 14 2019 +1. I'd love to have this feature too.
- Q. Schroll (2/18) May 14 2019 I've made it into a DIP: https://github.com/dlang/DIPs/pull/155
- Exil (5/26) May 14 2019 Does it need to be called opStaticIndex? Could it just be called
- Q. Schroll (4/20) May 14 2019 I don't have thought about names much. It is not completely
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (10/14) May 15 2019 Correct. But opIndex(T...)(Foo!T t) may be, and could then be
- Exil (10/24) May 15 2019 s[0](Foo!0.init) // -> s.opIndex!(0)()(Foo!0.init)
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (35/65) May 16 2019 I meant exactly what I wrote. Since s[0] would be compile-time,
- Exil (6/78) May 16 2019 Well now your example doesn't have opIndex(T...)() that your
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (23/39) May 16 2019 Sure it does. There's essentially no difference between fun1 and
- Exil (5/45) May 16 2019 This doesn't help with the explanation, what special syntax?
- Q. Schroll (9/13) May 17 2019 Simple answer is: You cannot overload opIndex aliasing a data
- Exil (31/44) May 17 2019 That's what template specialization is exactly for. Exactly how
- Q. Schroll (37/83) May 17 2019 Do you really mean specialization? I'm asking as you don't use it
- Q. Schroll (16/21) May 15 2019 I've thought about it and concluded the answer is no.
- angel (13/67) May 14 2019 You say:
- H. S. Teoh (17/27) May 14 2019 [...]
- Q. Schroll (13/27) May 14 2019 You get it wrong: The fact that `i` can be figured out by some
- Ahmet Sait (6/11) May 14 2019 I think this is solving the wrong problem. If compiler provided
- H. S. Teoh (14/27) May 14 2019 Again, people are getting confused by the overloaded term
- Ahmet Sait (17/44) May 14 2019 I've read it before, and read it again now just to be sure.
- H. S. Teoh (28/45) May 14 2019 Templates are not instantiated once per call. They are only
- Q. Schroll (42/55) May 14 2019 Maybe. I'll try to clarify what problem is being solved.
- Manu (6/20) May 15 2019 I have wanted this so many times. In C++, we can check if a value is a
I whish, D has these operators: opStaticIndex opStaticIndexAssign opStaticIndexOpAssign opStaticSlice When used, they look the same way as opIndex and friends, but the stuff between [ and ] would be bound to template parameters (instead of runtime parameters) and be known at compile time. Basically, this allows to implement static indexing the same way it is possible for an AliasSeq. Furthermore, it is possible to have static and dynamic indexing next to each other. This is supported by static-size arrays (e.g. int[3]) only: https://run.dlang.io/is/99g01e Implementing e.g. tuple-like structures, it would be valuable to have both. Dynamic indexing would only be present (design by introspection) if the contents support it. struct S { enum int opStaticIndex(int index) = index + 1; int opIndex(int index) { return index + 1; } enum size_t[2] opStaticSlice(size_t i, size_t j) = [ i, j ]; void opStaticIndexAssign(int index)(int value); void opStaticIndexAssign(int i, int j)(int value); void opStaticIndexAssign(size_t[2] slice)(int value); void opStaticIndexOpAssign(string op, int index)(int value); } S s; int i = s[0]; // rewrites to s.opStaticIndex!(0), as 0 can be used for the template parameter int j = s[i]; // rewrites to s.opIndex(i), as i cannot be read at compile-time The "Static" variants would be preferred when determining which one to lower to. s[0] = 1; // rewrites to s.opStaticIndexAssign!(0)(1) s[0, 2] = 1; // rewrites to s.opStaticIndexAssign!(0, 2)(1) s[0 .. 2] = 1; // rewrites to s.opStaticIndexAssign!(s.opStaticSlice!(0, 2))(1) s[0] += 1; // rewrites to s.opStaticIndexOpAssign!("+", 0)(1) s[0, 2] += 1; // rewrites to s.opStaticIndexOpAssign!("+", 0, 2)(1) Currently, there is no way to implement custom compile-time indexing. Only using alias-this to an AliasSeq can do something, but is very limited. The alias-this is shadowed by any presence of opIndex. An alternative would be a `static` or `enum` storage class for function runtime parameters that only bind to values known at compile-time. These have been proposed years ago and would not only solve this but also format!"fmt" vs. format(fmt) and friends. I have no knowledge about the DMD implementation, but I'd intuitively expect that new operator rewrites would be much less work to implement. Do you want it? Do you see problems? Should I clarify something? Is it worth writing a DIP?
May 14 2019
On Tue, May 14, 2019 at 03:49:51PM +0000, Q. Schroll via Digitalmars-d wrote:I whish, D has these operators: opStaticIndex opStaticIndexAssign opStaticIndexOpAssign opStaticSlice When used, they look the same way as opIndex and friends, but the stuff between [ and ] would be bound to template parameters (instead of runtime parameters) and be known at compile time. Basically, this allows to implement static indexing the same way it is possible for an AliasSeq.+1. I'd love to have this feature too. T -- Don't throw out the baby with the bathwater. Use your hands...
May 14 2019
On Tuesday, 14 May 2019 at 16:15:20 UTC, H. S. Teoh wrote:On Tue, May 14, 2019 at 03:49:51PM +0000, Q. Schroll via Digitalmars-d wrote:I've made it into a DIP: https://github.com/dlang/DIPs/pull/155I whish, D has these operators: opStaticIndex opStaticIndexAssign opStaticIndexOpAssign opStaticSlice When used, they look the same way as opIndex and friends, but the stuff between [ and ] would be bound to template parameters (instead of runtime parameters) and be known at compile time. Basically, this allows to implement static indexing the same way it is possible for an AliasSeq.+1. I'd love to have this feature too. T
May 14 2019
On Wednesday, 15 May 2019 at 01:07:10 UTC, Q. Schroll wrote:On Tuesday, 14 May 2019 at 16:15:20 UTC, H. S. Teoh wrote:Does it need to be called opStaticIndex? Could it just be called opIndex and require the specific template arguments. If you define opIndex(int)() now, it will never be called right (unless you specifically invoke it)?On Tue, May 14, 2019 at 03:49:51PM +0000, Q. Schroll via Digitalmars-d wrote:I've made it into a DIP: https://github.com/dlang/DIPs/pull/155I whish, D has these operators: opStaticIndex opStaticIndexAssign opStaticIndexOpAssign opStaticSlice When used, they look the same way as opIndex and friends, but the stuff between [ and ] would be bound to template parameters (instead of runtime parameters) and be known at compile time. Basically, this allows to implement static indexing the same way it is possible for an AliasSeq.+1. I'd love to have this feature too. T
May 14 2019
On Wednesday, 15 May 2019 at 01:42:57 UTC, Exil wrote:On Wednesday, 15 May 2019 at 01:07:10 UTC, Q. Schroll wrote:I don't have thought about names much. It is not completely obvious to me that reusing existing names works *in any circumstance*.On Tuesday, 14 May 2019 at 16:15:20 UTC, H. S. Teoh wrote:Does it need to be called opStaticIndex? Could it just be called opIndex and require the specific template arguments. If you define opIndex(int)() now, it will never be called right (unless you specifically invoke it)?On Tue, May 14, 2019 at 03:49:51PM +0000, Q. Schroll via Digitalmars-d wrote:I've made it into a DIP: https://github.com/dlang/DIPs/pull/155[...]+1. I'd love to have this feature too. T
May 14 2019
On Wednesday, 15 May 2019 at 01:42:57 UTC, Exil wrote:Does it need to be called opStaticIndex? Could it just be called opIndex and require the specific template arguments. If you define opIndex(int)() now, it will never be called right (unless you specifically invoke it)?Correct. But opIndex(T...)(Foo!T t) may be, and could then be called as either s[0](Foo!0.init) or s[Foo!0.init] Not sure if this is a big issue, but it's at least indicative of possible other issues. -- Simen
May 15 2019
On Wednesday, 15 May 2019 at 09:05:20 UTC, Simen Kjærås wrote:On Wednesday, 15 May 2019 at 01:42:57 UTC, Exil wrote:s[0](Foo!0.init) // -> s.opIndex!(0)()(Foo!0.init) s[Foo!0.init] // -> s.opIndex(Foo!0.init); If it compiled would entire depend on what opIndex returns, the arguments passed to opIndex() are the values in "[]" not the brackets, which is a separate call. Unless you meant this? s[0, Foo!0.init]; Which would then be difficult to know which function to call as it takes in a compile-time value and a run-time value.Does it need to be called opStaticIndex? Could it just be called opIndex and require the specific template arguments. If you define opIndex(int)() now, it will never be called right (unless you specifically invoke it)?Correct. But opIndex(T...)(Foo!T t) may be, and could then be called as either s[0](Foo!0.init) or s[Foo!0.init] Not sure if this is a big issue, but it's at least indicative of possible other issues. -- Simen
May 15 2019
On Wednesday, 15 May 2019 at 20:04:29 UTC, Exil wrote:On Wednesday, 15 May 2019 at 09:05:20 UTC, Simen Kjærås wrote:I meant exactly what I wrote. Since s[0] would be compile-time, the arguments passed to opIndex would indeed be 0. This would evaluate to a function taking a Foo!0. We could define that to be immediately called with no run-time arguments, but that complicates certain patterns that may be of interest: struct S { template opIndex(size_t idx) { static if (idx == 0) alias opIndex = fun1; else alias opIndex = fun2; } void fun1(size_t i)(Foo!i value) {} void fun2() {} } S s; s[0](Foo!0.init); // Should call fun1 with Foo!0.init. s[1](); // Should call fun2 with no args. If immediate call was implemented, the above would look like this: struct S { auto opIndex(size_t idx)() { static if (idx == 0) return &fun1!idx; else return &fun2; } void fun1(size_t i)(Foo!i value) {} void fun2() {} } Suddenly I'm dealing with function pointers instead of aliases, and this forces you to special-case functions while values, types, and aliases to everything but functions follow the same pattern. Of course, if no run-time arguments were given and there is no & operator before s[0], calling the returned function immediately might be the sane thing to do. -- SimenOn Wednesday, 15 May 2019 at 01:42:57 UTC, Exil wrote:s[0](Foo!0.init) // -> s.opIndex!(0)()(Foo!0.init) s[Foo!0.init] // -> s.opIndex(Foo!0.init); If it compiled would entire depend on what opIndex returns, the arguments passed to opIndex() are the values in "[]" not the brackets, which is a separate call. Unless you meant this? s[0, Foo!0.init]; Which would then be difficult to know which function to call as it takes in a compile-time value and a run-time value.Does it need to be called opStaticIndex? Could it just be called opIndex and require the specific template arguments. If you define opIndex(int)() now, it will never be called right (unless you specifically invoke it)?Correct. But opIndex(T...)(Foo!T t) may be, and could then be called as either s[0](Foo!0.init) or s[Foo!0.init] Not sure if this is a big issue, but it's at least indicative of possible other issues. -- Simen
May 16 2019
On Thursday, 16 May 2019 at 08:28:35 UTC, Simen Kjærås wrote:On Wednesday, 15 May 2019 at 20:04:29 UTC, Exil wrote:Well now your example doesn't have opIndex(T...)() that your original post did. I also don't see how this is a problem specifically if it was named opIndex. Seems more like a detail that would have to be ironed out either way to ensure templates can be used with opStaticIndex/opIndex.On Wednesday, 15 May 2019 at 09:05:20 UTC, Simen Kjærås wrote:I meant exactly what I wrote. Since s[0] would be compile-time, the arguments passed to opIndex would indeed be 0. This would evaluate to a function taking a Foo!0. We could define that to be immediately called with no run-time arguments, but that complicates certain patterns that may be of interest: struct S { template opIndex(size_t idx) { static if (idx == 0) alias opIndex = fun1; else alias opIndex = fun2; } void fun1(size_t i)(Foo!i value) {} void fun2() {} } S s; s[0](Foo!0.init); // Should call fun1 with Foo!0.init. s[1](); // Should call fun2 with no args. If immediate call was implemented, the above would look like this: struct S { auto opIndex(size_t idx)() { static if (idx == 0) return &fun1!idx; else return &fun2; } void fun1(size_t i)(Foo!i value) {} void fun2() {} } Suddenly I'm dealing with function pointers instead of aliases, and this forces you to special-case functions while values, types, and aliases to everything but functions follow the same pattern. Of course, if no run-time arguments were given and there is no & operator before s[0], calling the returned function immediately might be the sane thing to do. -- SimenOn Wednesday, 15 May 2019 at 01:42:57 UTC, Exil wrote:s[0](Foo!0.init) // -> s.opIndex!(0)()(Foo!0.init) s[Foo!0.init] // -> s.opIndex(Foo!0.init); If it compiled would entire depend on what opIndex returns, the arguments passed to opIndex() are the values in "[]" not the brackets, which is a separate call. Unless you meant this? s[0, Foo!0.init]; Which would then be difficult to know which function to call as it takes in a compile-time value and a run-time value.Does it need to be called opStaticIndex? Could it just be called opIndex and require the specific template arguments. If you define opIndex(int)() now, it will never be called right (unless you specifically invoke it)?Correct. But opIndex(T...)(Foo!T t) may be, and could then be called as either s[0](Foo!0.init) or s[Foo!0.init] Not sure if this is a big issue, but it's at least indicative of possible other issues. -- Simen
May 16 2019
On Thursday, 16 May 2019 at 21:13:24 UTC, Exil wrote:On Thursday, 16 May 2019 at 08:28:35 UTC, Simen Kjærås wrote:Sure it does. There's essentially no difference between fun1 and fun2 here: template fun1(T...) { alias fun1 = impl; } int impl() { return 3; } int fun2(T...)() { return 3; } unittest { auto a = fun1!(1,2,3); auto b = fun2!(1,2,3); } In the same way, whether I write auto opIndex()() {} or alias opIndex = ... is irrelevant. The behavior should be exactly the same.struct S { template opIndex(size_t idx) { static if (idx == 0) alias opIndex = fun1; else alias opIndex = fun2; } void fun1(size_t i)(Foo!i value) {} void fun2() {} }Well now your example doesn't have opIndex(T...)() that your original post did.I also don't see how this is a problem specifically if it was named opIndex. Seems more like a detail that would have to be ironed out either way to ensure templates can be used with opStaticIndex/opIndex.It's a problem because opIndex comes with special syntax that other symbols don't have. -- Simen
May 16 2019
On Thursday, 16 May 2019 at 21:36:43 UTC, Simen Kjærås wrote:On Thursday, 16 May 2019 at 21:13:24 UTC, Exil wrote:This doesn't help with the explanation, what special syntax? Maybe an example that works with opStaticIndex and that wouldn't work with opIndex would help. Otherwise I still don't see a problem.On Thursday, 16 May 2019 at 08:28:35 UTC, Simen Kjærås wrote:Sure it does. There's essentially no difference between fun1 and fun2 here: template fun1(T...) { alias fun1 = impl; } int impl() { return 3; } int fun2(T...)() { return 3; } unittest { auto a = fun1!(1,2,3); auto b = fun2!(1,2,3); } In the same way, whether I write auto opIndex()() {} or alias opIndex = ... is irrelevant. The behavior should be exactly the same.struct S { template opIndex(size_t idx) { static if (idx == 0) alias opIndex = fun1; else alias opIndex = fun2; } void fun1(size_t i)(Foo!i value) {} void fun2() {} }Well now your example doesn't have opIndex(T...)() that your original post did.I also don't see how this is a problem specifically if it was named opIndex. Seems more like a detail that would have to be ironed out either way to ensure templates can be used with opStaticIndex/opIndex.It's a problem because opIndex comes with special syntax that other symbols don't have. -- Simen
May 16 2019
On Thursday, 16 May 2019 at 22:26:34 UTC, Exil wrote:This doesn't help with the explanation, what special syntax? Maybe an example that works with opStaticIndex and that wouldn't work with opIndex would help. Otherwise I still don't see a problem.Simple answer is: You cannot overload opIndex aliasing a data member and opIndex being a function. If it is a function somewhere, it *must* always be a function in that aggregate (struct/union/class/interface). It is more flexible not having a name clash. It's easier to learn and reason about, too. To me, it has literally no benefit using opIndex for static indexing. Part of a working example: https://run.dlang.io/is/274pNN
May 17 2019
On Friday, 17 May 2019 at 17:41:26 UTC, Q. Schroll wrote:On Thursday, 16 May 2019 at 22:26:34 UTC, Exil wrote:That's what template specialization is exactly for. Exactly how it can be used: struct A { template opIndex(int i) { static if(i == 0) alias opIndex = member1; else alias opIndex = member2; } int member1; double member2; int opIndex( int a, int b ) { return 0; } int opIndex(T...)( T a ) { return 1; } } void main() { A a; a[0,0]; a[0]; a.opIndex!0 = 10; // a[0] = 10 a.opIndex!1 = 10.1; // a[1] = 10.1 writeln( a.member1, a.member2 ); }This doesn't help with the explanation, what special syntax? Maybe an example that works with opStaticIndex and that wouldn't work with opIndex would help. Otherwise I still don't see a problem.Simple answer is: You cannot overload opIndex aliasing a data member and opIndex being a function. If it is a function somewhere, it *must* always be a function in that aggregate (struct/union/class/interface).It is more flexible not having a name clash. It's easier to learn and reason about, too. To me, it has literally no benefit using opIndex for static indexing. Part of a working example: https://run.dlang.io/is/274pNNSo what if you have 2 parameters, one is a runtime value and the other is a compile time value? Would it be a compile error then? But then you can implement opIndex and opStaticIndex, so you can use two runtime values and two compile time values but if for some reason you need one, instead you will need some hacky work around to get it to work: arr[ 0, someRunTimeIndex() ] = 10; // error mixing compile-time and run-time int i = 0; arr[ i, someRunTimeIndex() ] = 10; // ok
May 17 2019
On Friday, 17 May 2019 at 19:52:53 UTC, Exil wrote:On Friday, 17 May 2019 at 17:41:26 UTC, Q. Schroll wrote:Do you really mean specialization? I'm asking as you don't use it in your following example. Maybe you mean instantiation? I just want to make sure we're talking about the same thing.On Thursday, 16 May 2019 at 22:26:34 UTC, Exil wrote:That's what template specialization is exactly for. Exactly how it can be used:This doesn't help with the explanation, what special syntax? Maybe an example that works with opStaticIndex and that wouldn't work with opIndex would help. Otherwise I still don't see a problem.Simple answer is: You cannot overload opIndex aliasing a data member and opIndex being a function. If it is a function somewhere, it *must* always be a function in that aggregate (struct/union/class/interface).struct A { template opIndex(int i) { static if (i == 0) alias opIndex = member1; else alias opIndex = member2; } int member1; double member2; int opIndex(int a, int b) { return 0; } int opIndex(T...)(T a) { return 1; } } void main() { A a; a[0,0]; a[0]; a.opIndex!0 = 10; // a[0] = 10 a.opIndex!1 = 10.1; // a[1] = 10.1 writeln(a.member1, a.member2); }I'm sorry, but I don't get what you want to show me. I strongly believe that you have a point somewhere and I'm just not seeing it. I've just tried out that code piece and it worked to my surprise. I honestly think now, it's actually possible. The question remaining is: Is it really better or rather too complicated? In the current form of the DIP, if you'd use a type as index, it'd compile. I'll change that, because that's not how it's meant to be. I find it really difficult to say if widening the abilities of opIndex to opStaticIndex can be done. There are so many corner cases. That's mainly why I proposed a new compiler-recognized name: It's the safe option. In the current state of D, opIndex must be callable to trigger the rewrite; being present as member is insufficient. As a simple example, if you use std.typecons.Tuple with a member namend "opIndex" of type int, you still can do everything you'd expect from a tuple with reasonable field names. Notably, it doesn't invalidate static indexing. But make "opIndex" of type `int delegate(/*whatever you like*/)`, and the Tuple is unusable: https://run.dlang.io/is/BuiT2a It's somewhat artificial and not a reason for the DIP. Sure, naming a member "opStaticIndex" would break the tuple, too.To use a template, you need *all* arguments at compile-time. Otherwise it's lowered to a run-time indexing operation no matter how many arguments are present at compile-time. The first call (// error mixing) is not a problem. The fact that some values could be used as a template parameter is irrelevant. The question is: Does the template instantiation work? And the answer is no. That's where double indexing, e.g. tup[0][1] versus tup[0,1] makes a real difference. If you really need mixing sorts of arguments, that's the way I'd do it. Mixing is not an error. Mixing is just run-time.It is more flexible not having a name clash. It's easier to learn and reason about, too. To me, it has literally no benefit using opIndex for static indexing. Part of a working example: https://run.dlang.io/is/274pNNSo what if you have 2 parameters, one is a runtime value and the other is a compile time value? Would it be a compile error then? But then you can implement opIndex and opStaticIndex, so you can use two runtime values and two compile time values but if for some reason you need one, instead you will need some hacky work around to get it to work: arr[ 0, someRunTimeIndex() ] = 10; // error mixing compile-time and run-time int i = 0; arr[ i, someRunTimeIndex() ] = 10; // ok
May 17 2019
On Wednesday, 15 May 2019 at 01:42:57 UTC, Exil wrote:On Wednesday, 15 May 2019 at 01:07:10 UTC, Q. Schroll wrote: Does it need to be called opStaticIndex? Could it just be called opIndex and require the specific template arguments. If you define opIndex(int)() now, it will never be called right (unless you specifically invoke it)?I've thought about it and concluded the answer is no. For dynamic indexing (i.e. current state opIndex and friends) most of them (everything except opDollar) must be some kind of function (i.e. functions, function templates, something with opCall, ... you name it), as it is rewritten to a function call. With opStaticIndex, apart from opStaticIndex(Op)Assign, where the right-hand side is still a function parameter, it could be implemented by an enum or alias or something like that (cf. current state opDollar). You cannot overload functions with enums or something the like. Then you need two different names. You could fallback to that if the opStatic.. ones are not there, but that complicates the DIP even more. I'd stay with the proposed names or change them to something different, but not reuse something that currently exists.
May 15 2019
On Tuesday, 14 May 2019 at 15:49:51 UTC, Q. Schroll wrote:I whish, D has these operators: opStaticIndex opStaticIndexAssign opStaticIndexOpAssign opStaticSlice When used, they look the same way as opIndex and friends, but the stuff between [ and ] would be bound to template parameters (instead of runtime parameters) and be known at compile time. Basically, this allows to implement static indexing the same way it is possible for an AliasSeq. Furthermore, it is possible to have static and dynamic indexing next to each other. This is supported by static-size arrays (e.g. int[3]) only: https://run.dlang.io/is/99g01e Implementing e.g. tuple-like structures, it would be valuable to have both. Dynamic indexing would only be present (design by introspection) if the contents support it. struct S { enum int opStaticIndex(int index) = index + 1; int opIndex(int index) { return index + 1; } enum size_t[2] opStaticSlice(size_t i, size_t j) = [ i, j ]; void opStaticIndexAssign(int index)(int value); void opStaticIndexAssign(int i, int j)(int value); void opStaticIndexAssign(size_t[2] slice)(int value); void opStaticIndexOpAssign(string op, int index)(int value); } S s; int i = s[0]; // rewrites to s.opStaticIndex!(0), as 0 can be used for the template parameter int j = s[i]; // rewrites to s.opIndex(i), as i cannot be read at compile-time The "Static" variants would be preferred when determining which one to lower to. s[0] = 1; // rewrites to s.opStaticIndexAssign!(0)(1) s[0, 2] = 1; // rewrites to s.opStaticIndexAssign!(0, 2)(1) s[0 .. 2] = 1; // rewrites to s.opStaticIndexAssign!(s.opStaticSlice!(0, 2))(1) s[0] += 1; // rewrites to s.opStaticIndexOpAssign!("+", 0)(1) s[0, 2] += 1; // rewrites to s.opStaticIndexOpAssign!("+", 0, 2)(1) Currently, there is no way to implement custom compile-time indexing. Only using alias-this to an AliasSeq can do something, but is very limited. The alias-this is shadowed by any presence of opIndex. An alternative would be a `static` or `enum` storage class for function runtime parameters that only bind to values known at compile-time. These have been proposed years ago and would not only solve this but also format!"fmt" vs. format(fmt) and friends. I have no knowledge about the DMD implementation, but I'd intuitively expect that new operator rewrites would be much less work to implement. Do you want it? Do you see problems? Should I clarify something? Is it worth writing a DIP?You say: int i = s[0]; // rewrites to s.opStaticIndex!(0), as 0 can be int j = s[i]; // rewrites to s.opIndex(i), as i cannot be read at compile-time But actually, in this example, s[i] CAN be figured out at compile-time. What is even more problematic is that the ability of the compiler to figure out the value of 'i' and s[i] at compile-time might depend on the optimization level, thus you cannot be sure whether s.opStaticIndex!(1) or s.opIndex(i) will be called. IMHO, s[0] is already calculated in compile-time when appropriate optimization level is invoked ... at least it should've been.
May 14 2019
On Tue, May 14, 2019 at 05:27:25PM +0000, angel via Digitalmars-d wrote: [...]You say: int i = s[0]; // rewrites to s.opStaticIndex!(0), as 0 can be int j = s[i]; // rewrites to s.opIndex(i), as i cannot be read at compile-time But actually, in this example, s[i] CAN be figured out at compile-time. What is even more problematic is that the ability of the compiler to figure out the value of 'i' and s[i] at compile-time might depend on the optimization level, thus you cannot be sure whether s.opStaticIndex!(1) or s.opIndex(i) will be called.[...] I think you're being misled by the ambiguous term "compile-time", which can refer to very different things. See: https://wiki.dlang.org/User:Quickfur/Compile-time_vs._compile-time In D, indexing with a variable is not considered "compile-time" in the sense of AST manipulation (see above article for definition), although it *can* be evaluated in CTFE. AIUI, opStaticIndex is intended to be used at the AST manipulation phase, since the usual "runtime" array indexing is already supported in CTFE. This has nothing to do with optimization levels, which happens in codegen, long past any meaningful usage of compile-time computation as far as AST manipulation or CTFE is concerned. T -- Too many people have open minds but closed eyes.
May 14 2019
On Tuesday, 14 May 2019 at 17:27:25 UTC, angel wrote:You say: int i = s[0]; // rewrites to s.opStaticIndex!(0), as 0 can be int j = s[i]; // rewrites to s.opIndex(i), as i cannot be read at compile-time But actually, in this example, s[i] CAN be figured out at compile-time.You get it wrong: The fact that `i` can be figured out by some clever compiler is irrelevant. You couldn't assign it to some static immutable or enum. You couldn't use it as a template parameter. This is not about clever optimizations, it is about wether the spec says, the value is present at compile time.What is even more problematic is that the ability of the compiler to figure out the value of 'i' and s[i] at compile-time might depend on the optimization level, thus you cannot be sure whether s.opStaticIndex!(1) or s.opIndex(i) will be called. IMHO, s[0] is already calculated in compile-time when appropriate optimization level is invoked ... at least it should've been.If `seq` is a non-empty AliasSeq, you can use seq[0]. You cannot use seq[i] for `i` defined as above. The spec says that `i`'s value is determined at runtime, i.e. when the compiled program is executed. If the compiler's optimizer can figure out that it knows the value beforehand, it won't make the thing compile. When talking about language specification, one rarely considers optimizations.
May 14 2019
On Tuesday, 14 May 2019 at 15:49:51 UTC, Q. Schroll wrote:I whish, D has these operators: opStaticIndex opStaticIndexAssign opStaticIndexOpAssign opStaticSliceI think this is solving the wrong problem. If compiler provided means to check whether a parameter is known at compile time easily & consistently these would not be necessary at all. Of course, talk is cheap and actually implementing such new __traits is rather compilcated.
May 14 2019
On Tue, May 14, 2019 at 06:13:18PM +0000, Ahmet Sait via Digitalmars-d wrote:On Tuesday, 14 May 2019 at 15:49:51 UTC, Q. Schroll wrote:Again, people are getting confused by the overloaded term "compile-time". People really need to read this: https://wiki.dlang.org/User:Quickfur/Compile-time_vs._compile-time (Sorry for the shameless self-plug.) opStaticIndex is necessary because it pertains to the *AST manipulation* phase of compilation, whereas opIndex pertains the executable phase (CTFE and/or runtime). It's meaningless to "check whether a parameter is known at compile time" because you cannot go back to the AST manipulation stage after you have passed semantic analysis, which is when types are resolved and things like VRP are applied. T -- Beware of bugs in the above code; I have only proved it correct, not tried it. -- Donald KnuthI whish, D has these operators: opStaticIndex opStaticIndexAssign opStaticIndexOpAssign opStaticSliceI think this is solving the wrong problem. If compiler provided means to check whether a parameter is known at compile time easily & consistently these would not be necessary at all. Of course, talk is cheap and actually implementing such new __traits is rather compilcated.
May 14 2019
On Tuesday, 14 May 2019 at 18:35:51 UTC, H. S. Teoh wrote:On Tue, May 14, 2019 at 06:13:18PM +0000, Ahmet Sait via Digitalmars-d wrote:I've read it before, and read it again now just to be sure. Obviously such functions (the ones that check whether a variable is known at CT) need to be templates and get instantiated on every call to actually work. Something close to this: auto opIndex()(size_t i) { enum types = [ "double", "long", "int" ]; static if (__traits(isKnownAtCT, i)) { static if (i < types.length) return mixin("cast(" ~ types[i] ~ ")i"); else return "Here be dragons"; } }On Tuesday, 14 May 2019 at 15:49:51 UTC, Q. Schroll wrote:Again, people are getting confused by the overloaded term "compile-time". People really need to read this: https://wiki.dlang.org/User:Quickfur/Compile-time_vs._compile-time (Sorry for the shameless self-plug.) opStaticIndex is necessary because it pertains to the *AST manipulation* phase of compilation, whereas opIndex pertains the executable phase (CTFE and/or runtime). It's meaningless to "check whether a parameter is known at compile time" because you cannot go back to the AST manipulation stage after you have passed semantic analysis, which is when types are resolved and things like VRP are applied. TI whish, D has these operators: opStaticIndex opStaticIndexAssign opStaticIndexOpAssign opStaticSliceI think this is solving the wrong problem. If compiler provided means to check whether a parameter is known at compile time easily & consistently these would not be necessary at all. Of course, talk is cheap and actually implementing such new __traits is rather compilcated.
May 14 2019
On Tue, May 14, 2019 at 08:07:03PM +0000, Ahmet Sait via Digitalmars-d wrote: [...]I've read it before, and read it again now just to be sure. Obviously such functions (the ones that check whether a variable is known at CT) need to be templates and get instantiated on every call to actually work.Templates are not instantiated once per call. They are only instantiated once per unique combination of argument types.Something close to this: auto opIndex()(size_t i)This doesn't work, as the compiler sees `i` as a runtime parameter. When compiling this code, the compiler doesn't know (nor care) where the argument might have come from. It can't, because this function could potentially be called from an external module separately compiled, or even bound only at runtime via a dynamic library load. Also, since the template parameter list is empty, this template will only be instantiated once throughout the entire program. So the static if below won't work as you want it to.{ enum types = [ "double", "long", "int" ]; static if (__traits(isKnownAtCT, i)) { static if (i < types.length) return mixin("cast(" ~ types[i] ~ ")i"); else return "Here be dragons"; } }Something closer to what you're looking for might be this: auto opIndex(alias i)() if (is(typeof(i) == size_t) { ... } This will get instantiated once per call, but will require !() syntax rather than the usual, so the way the [] operator is overloaded will have to be extended. Also, `i` in this scope will refer to the actual variable in the caller's scope, so if the function body modifies it you could get very strange results. And I"m not 100% sure this will work for all possible argument types; the alias parameter may not accept arbitrary expressions due to various limitations, so you might end up with the strange situation that opIndex!0 works but opIndex!(1-1) doesn't. T -- English is useful because it is a mess. Since English is a mess, it maps well onto the problem space, which is also a mess, which we call reality. Similarly, Perl was designed to be a mess, though in the nicest of all possible ways. -- Larry Wall
May 14 2019
On Tuesday, 14 May 2019 at 18:13:18 UTC, Ahmet Sait wrote:On Tuesday, 14 May 2019 at 15:49:51 UTC, Q. Schroll wrote:Maybe. I'll try to clarify what problem is being solved. Current state of D: Let seq be some AliasSeq. Indexing it looks like this: auto value = seq[i]; Here, the compiler must evaluate `i` at compile-time (cf. H. S. Teoh's link) to even get the type of value correctly. Let arr be some slice (aka. "dynamic array"), e.g. a value of int[]. Indexing it looks like this: auto value = arr[i]; Wow, they look identically, but mean completely different things! What an incident. You can make the latter work in your custom type (notably random-access ranges) by providing opIndex and companions. However, you cannot provide the former functionality for your custom type. The suggested operators would solve that without interrupting the rest of the language.I whish, D has these operators: opStaticIndex opStaticIndexAssign opStaticIndexOpAssign opStaticSliceI think this is solving the wrong problem.If compiler provided means to check whether a parameter is known at compile time easily & consistently these would not be necessary at allWhat kind of parameter? A template parameter is known at compile-time by definition and a function (run-time) parameter by definition is not. The new operators won't change that.Of course, talk is cheap and actually implementing such new __traits is rather compilcated.This is no kind of __traits. I don't think the problem could be solved nicely with such __traits if it existed. The alternative were a parameter *storage class*: T opIndex(static size_t i); // alternative to opStaticIndex T opIndex( size_t i); An illustrative example: void format(static string fmt, ...); //1 void format( string fmt, ...); //2 You'd have both overloads. //1 is viable, when the first parameter can be evaluated at compile-time. It could check at compile-time if the arguments' types match the format. //2 is always viable, but //1 is considered a better match for a compile-time format string. The `static` storage class would make the parameter semantically equivalent to a template parameter inside the `format` function. Outside, it would be called using the same syntax, i.e. string s1 = format("The %s-th component", i); // calls //1 string s2 = format(readln(), i); // calls //2 You basically could overload on the compile-time availableness of parameters. There is no __traits to be involved. Even without compiler knowledge, I strongly believe that this is more complicated, apart from being a major change of the language.
May 14 2019
On Tue, May 14, 2019 at 9:15 AM H. S. Teoh via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Tue, May 14, 2019 at 03:49:51PM +0000, Q. Schroll via Digitalmars-d wrote:I have wanted this so many times. In C++, we can check if a value is a literal with some stupid __builtin_whatever() thing, and that can drive patterns like this, but that's terrible, and this proposal is obviously the right way to go about it.I whish, D has these operators: opStaticIndex opStaticIndexAssign opStaticIndexOpAssign opStaticSlice When used, they look the same way as opIndex and friends, but the stuff between [ and ] would be bound to template parameters (instead of runtime parameters) and be known at compile time. Basically, this allows to implement static indexing the same way it is possible for an AliasSeq.+1. I'd love to have this feature too.
May 15 2019