www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - New operators opStaticIndex and friends

reply Q. Schroll <qs.il.paperinik gmail.com> writes:
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
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
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 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
I've made it into a DIP: https://github.com/dlang/DIPs/pull/155
May 14
parent reply Exil <Exil gmall.com> writes:
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:
 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
I've made it into a DIP: https://github.com/dlang/DIPs/pull/155
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)?
May 14
next sibling parent Q. Schroll <qs.il.paperinik gmail.com> writes:
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:
 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:
 [...]
+1. I'd love to have this feature too. T
I've made it into a DIP: https://github.com/dlang/DIPs/pull/155
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 don't have thought about names much. It is not completely obvious to me that reusing existing names works *in any circumstance*.
May 14
prev sibling next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
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
parent reply Exil <Exil gmall.com> writes:
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:
 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
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.
May 15
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
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:
 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
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.
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. -- Simen
May 16
parent reply Exil <Exil gmall.com> writes:
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:
 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:
 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
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.
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. -- Simen
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.
May 16
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
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:
 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.
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.
 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
parent reply Exil <Exil gmall.com> writes:
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:
 On Thursday, 16 May 2019 at 08:28:35 UTC, Simen Kjærås wrote:
 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.
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.
 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
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.
May 16
parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
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
parent reply Exil <Exil gmall.com> writes:
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:
 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).
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 ); }
 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
So 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
parent Q. Schroll <qs.il.paperinik gmail.com> writes:
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:
 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).
That's what template specialization is exactly for. Exactly how it can be used:
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.
 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.
 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
So 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
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.
May 17
prev sibling parent Q. Schroll <qs.il.paperinik gmail.com> writes:
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
prev sibling next sibling parent reply angel <andrey.gelman gmail.com> writes:
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
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
prev sibling parent Q. Schroll <qs.il.paperinik gmail.com> writes:
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
prev sibling next sibling parent reply Ahmet Sait <nightmarex1337 hotmail.com> writes:
On Tuesday, 14 May 2019 at 15:49:51 UTC, Q. Schroll wrote:
 I whish, D has these operators:

 opStaticIndex
 opStaticIndexAssign
 opStaticIndexOpAssign
 opStaticSlice
I 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
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
 I whish, D has these operators:
 
 opStaticIndex
 opStaticIndexAssign
 opStaticIndexOpAssign
 opStaticSlice
I 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.
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 Knuth
May 14
parent reply Ahmet Sait <nightmarex1337 hotmail.com> writes:
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:
 On Tuesday, 14 May 2019 at 15:49:51 UTC, Q. Schroll wrote:
 I whish, D has these operators:
 
 opStaticIndex
 opStaticIndexAssign
 opStaticIndexOpAssign
 opStaticSlice
I 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.
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
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"; } }
May 14
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
prev sibling parent Q. Schroll <qs.il.paperinik gmail.com> writes:
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:
 I whish, D has these operators:

 opStaticIndex
 opStaticIndexAssign
 opStaticIndexOpAssign
 opStaticSlice
I think this is solving the wrong problem.
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.
 If compiler provided means to check whether a parameter is 
 known at compile time easily & consistently these would not be 
 necessary at all
What 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
prev sibling parent Manu <turkeyman gmail.com> writes:
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 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.
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.
May 15